Skip to content

Help Needed: Hard time understanding Discriminated Union #846

@alveshelio

Description

@alveshelio

I'm having a hard time understanding how discriminated union work.

I have this schema bellow and I have a couple of conditions:
One is for the workStatus this is required. The user has 3 options, "Full Time", "Contract" and "Both"
Now based on what the user selects we must display the appropriate fields
=> User selects "Full Time" then we need to request for fullTimeCompensation and contractCompensation should be undefined
=> User selects 'Contract" then we need to request for 'contractCompensationandfullTimeCompensationshould be undefined => User selects 'Both" then we need to request forfullTimeCompensation and 'contractCompensation

The other condition is workLocationType, this is a required field and the user has two options, either "Remote" or "Flexible"
If the user selects "Flexible" we need to as for field acceptableTravelTime, if user selects "Remote" then acceptableTravelTime should be undefined.

This is how I've defined my schemas

  const workStatusEnumSchema = z.nativeEnum(Work_Status_Types_Enum, {
    errorMap: () => ({ message: formValidationMessages.format('fieldRequired') }),
  })
  const workLocationEnumSchema = z.nativeEnum(Work_Location_Types_Enum, {
    errorMap: (issue, _ctx) => {
      console.warn('issue', issue)
      console.warn('context', _ctx)
      return { message: formValidationMessages.format('fieldRequired') }
    },
  })
  const workVacationEnumSchema = z.nativeEnum(Work_Vacation_Durations_Enum, {
    errorMap: () => ({ message: formValidationMessages.format('fieldRequired') }),
  })
  const baseSchema = z.object({
    workStatus: workStatusEnumSchema,
    workLocationType: workLocationEnumSchema,
    vacationDuration: workVacationEnumSchema,
    address: z.string(),
    latitude: z.number(),
    longitude: z.number(),
    drivingToWork: z.boolean(),
    occasionallyTravelOffice: z.boolean(),
    relocateForWork: z.boolean(),
    visaSponsorShip: z.array(visaSponsorshipSchema),
    spokenLanguages: z.array(optionSchema),
  })

  const fullTimeSchema = baseSchema.extend({
    workStatus: z.literal(Work_Status_Types_Enum.FullTime),
    fullTimeCompensation: z.string({
      required_error: formValidationMessages.format('fieldRequired'),
    }),
    contractCompensation: z.undefined(),
  })

  const contractSchema = baseSchema.extend({
    workStatus: z.literal(Work_Status_Types_Enum.Contract),
    contractCompensation: z.string({
      required_error: formValidationMessages.format('fieldRequired'),
    }),
    fullTimeCompensation: z.undefined(),
  })

  const fullTimeContractSchema = baseSchema.extend({
    workStatus: z.literal(Work_Status_Types_Enum.FullTimeOrContract),
    contractCompensation: z.string({
      required_error: formValidationMessages.format('fieldRequired'),
    }),
    fullTimeCompensation: z.string({
      required_error: formValidationMessages.format('fieldRequired'),
    }),
  })

  const workStatusSchema = z.union([fullTimeSchema, contractSchema, fullTimeContractSchema])

  const flexibleSchema = baseSchema.extend({
    workLocationType: z.literal(Work_Location_Types_Enum.Flexible),
    acceptableTravelTime: z.string({
      required_error: formValidationMessages.format('fieldRequired'),
    }),
  })

  const remoteSchema = baseSchema.extend({
    workLocationType: z.literal(Work_Location_Types_Enum.Remote),
    acceptableTravelTime: z.undefined(),
  })

  const workLocationSchema = z.union([flexibleSchema, remoteSchema])

  const schema = z.union([baseSchema, workLocationSchema, workStatusSchema])

  type FormData = z.infer<typeof schema>
const methods = useForm<FormData>({
    mode: 'onBlur',
    reValidateMode: 'onBlur',
    defaultValues: {
      vacationDuration: '' as Work_Vacation_Durations_Enum,
      workLocationType: '' as Work_Location_Types_Enum,
      workStatus: '' as Work_Status_Types_Enum,
      address: '',
      drivingToWork: false,
      occasionallyTravelOffice: false,
      relocateForWork: false,
      visaSponsorShip: [],
      spokenLanguages: [
        buildSpokenLanguageOption(router.locale as string, spokenLanguagesMessages),
      ],
    },
    resolver: zodResolver(schema),
  })

At this point I have no errors in the schema nor in the default values.
However, in my onSubmit function which is defined as follow:

const onSubmit = async ({
    vacationDuration,
    workLocationType,
    workStatus,
    fullTimeCompensation,
    contractCompensation,
    acceptableTravelTime,
    address,
    latitude,
    longitude,
    drivingToWork,
    relocateForWork,
    visaSponsorShip,
    occasionallyTravelOffice,
    spokenLanguages,
  }: FormData): Promise<void> => {
    ...
}

But this clearly doesn't work. If a user selects 'Full Time" then I get an error "Expeted 'CONTRACT', received 'FULL_TIME'", and in fullTimeCompensation if the user enters a value he get's an error "Expected undefined, received 'string'". Same thing if the user selects 'CONTRACT' and same behaviour for workLocationType if user selects something else other than "REMOTE" he will get the same error.

I'm not sure how to create the schema for my use case :(

Any help would be greatly appreciated.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions