Search code examples
react-hook-formzod

Display error messages in multiple locations using zod


I have a form created with zod. Both are optional inputs, but I want to make sure that both are input if one is input. And I want to display the error message on both sides.

So I wrote the code as below.

const schema = z.object({
  samplea: z.string().optional(),
  sampleb: z.string().optional(),
}).refine(
  (args) => {
    if (args.samplea || args.sampleb) {
      return args.samplea && args.sampleb ? true : false;
    }
    return true;
  },
  {
    message: "If you enter, please enter both",
    path: ["samplea", "sampleb"],
  }
);

...

<Box>
  <FormLabel>samplea</FormLabel>
  <TextField
    type="text"
    {...register("samplea")}
    error={!!errors.samplea}
    helperText={errors.samplea?.message}
  />
</Box>

<Box>
  <FormLabel>sampleb</FormLabel>
  <TextField
    type="text"
    {...register("sampleb")}
    error={!!errors.sampleb}
    helperText={errors.sampleb?.message}
  />
</Box>

There is a problem that the error message can only be displayed for "sampleb". I would like to know how to make both "samplea" and "sampleb" error and display error messages for both.


Solution

  • To solve this use superRefine. It can be used for creating multiple issues and customizing error codes.

    const schema = z
      .object({
        samplea: z.string().optional(),
        sampleb: z.string().optional(),
      })
      .superRefine((args, ctx) => {
        if (!args.samplea && !args.sampleb) {
          ctx.addIssue({
            code: z.ZodIssueCode.custom,
            path: ["samplea"],
            fatal: true,
            message: "If you enter, please enter both",
          });
          ctx.addIssue({
            code: z.ZodIssueCode.custom,
            path: ["sampleb"],
            fatal: true,
            message: "If you enter, please enter both",
          });
        }
    });
    

    In the above code block, superRefine has two params args (can be named anything), which represents the values of the object and we also have ctx which contains methods from superRefine.

    Next, using an if statement, we determine if both fields contain any value. In the event that the condition !args.samplea && !args.sampleb evaluates to is true, ctx.addIssue is triggered. This allows us to define the following:

    • code: Enables you to customize your issue.
    • path: The desired field.
    • fatal: Used to abort early, preventing other refinements from executing.
    • message: Enter a validation message.