How to do validation correctly so that one of the two fields is required using React hook form and Zod

393 views Asked by At

The behavior I'm trying to achieve is that if you don't fill in the 'slot Number' and 'slot Size' fields and click on send, both of these fields should be highlighted with an error. Next, if you add a value to one of these fields, so that the error message is deleted not only from the field in which the tex was entered, but also in the neighboring one too. Now it disappears only after clicking the submit button again

link to stackblitz.com

const formSchema = z
  .object({
    description: z.string().nonempty('empty field'),
    slotNumber: z.string(),
    slotSize: z.string(),
  })
  .superRefine(({ slotNumber, slotSize }, ctx) => {
    if (slotNumber === '' && slotSize === '') {
      ctx.addIssue({
        code: z.ZodIssueCode.custom,
        message: 'one of two fields [slot size, slot number] must be required ',
        path: ['slotSize'],
      });
      ctx.addIssue({
        code: z.ZodIssueCode.custom,
        message: 'one of two fields [slot size, slotnumber] must be required ',
        path: ['slotNumber'],
      });
      return z.NEVER;
    }
  });
type FormFields = z.infer<typeof formSchema>;
export default function App() {
  const {
    control,
    handleSubmit,
    formState: { errors },
    reset,
  } = useForm({
    mode: 'all',
    reValidateMode: 'onBlur',
    resolver: zodResolver(formSchema),
    defaultValues: {
      description: '',
      slotNumber: '',
      slotSize: '',
    },
  });

  const onSubmit: SubmitHandler<FormFields> = (data) => {
    console.log(data);
    reset();
  };

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <Box
        sx={{
          display: 'flex',
          gap: '1rem',
          p: '1rem',
          flexDirection: 'column',
        }}
      >
        <Controller
          name="description"
          control={control}
          render={({ field }) => (
            <TextField
              {...field}
              variant="outlined"
              error={!!errors.description}
              helperText={errors.description?.message}
              size="small"
              label="description"
            />
          )}
        />
        <Controller
          name="slotNumber"
          control={control}
          render={({ field }) => (
            <TextField
              {...field}
              variant="outlined"
              error={!!errors.slotNumber}
              helperText={errors.slotNumber?.message}
              size="small"
              label="slot number"
            />
          )}
        />

        <Controller
          name="slotSize"
          control={control}
          render={({ field }) => (
            <TextField
              {...field}
              variant="outlined"
              error={!!errors.slotSize}
              helperText={errors.slotSize?.message}
              size="small"
              label="slot size"
            />
          )}
        />
        <Button type="submit">Submit</Button>
      </Box>
    </form>
  );
}
1

There are 1 answers

0
Prashant Jangam On BEST ANSWER

pull out clearErrors from useForm and isValid from formState then use them with useEffect

...
const {
    control,
    handleSubmit,
    formState: { errors, isValid },
    reset,
    clearErrors
    
  } = useForm({
    mode: 'all',
    reValidateMode: 'onBlur',
    resolver: zodResolver(formSchema),
    defaultValues: {
      description: '',
      slotNumber: '',
      slotSize: '',
    },
  });

  React.useEffect(() => {
    clearErrors();
  }, [isValid]);
...