react-hook-formzod

How to write validation on zod depending on react state


There is a state that can take the values true or false. If state is true, the field is required, otherwise it is not. How to validate depending on the condition? I found an example:

const [selectOption, setSelectOption] = useState(null);

const stepTwoForm = useForm({
  resolver: zodResolver(stepTwoSchema.refine(
    (data) => ({ ...data, select: selectOption }),
  )),
});

And then

export const stepTwoSchema = z.object({
  inputOne: z.string().optional(),
  inputTwo: z.string().optional(),
}).superRefine((data, ctx) => {
  const selectedOption = ctx.parent?.select;

  if (selectedOption === 'A' && !data.inputOne?.trim()) {
    ctx.addIssue({
      path: ['inputOne'],
      message: 'Input 1 is required when Option A is selected.',
    });
  }

  if (selectedOption === 'B' && !data.inputTwo?.trim()) {
    ctx.addIssue({
      path: ['inputTwo'],
      message: 'Input 2 is required when Option B is selected.',
    });
  }
});

But this is for the old version of zod. In the new version, ctx does not have the 'parent' property.


Solution

  • To validate fields conditionally in Zod, you have two main approaches:

    1. Include the condition in the form data:

      const schema = z.object({
        selectOption: z.string(), // Your condition
        inputOne: z.string().optional(),
        inputTwo: z.string().optional()
      }).superRefine((data, ctx) => {
        if (data.selectOption === "A" && !data.inputOne?.trim()) {
          ctx.addIssue({
            code: "custom",
            path: ["inputOne"],
            message: "Input 1 is required for Option A"
          });
        }
        // Similar for other conditions
      });
      
      // Usage:
      useForm({
        resolver: zodResolver(schema)
      });
      
    2. Pass the condition as a parameter:

      const createSchema = (selectOption) => 
        z.object({
          inputOne: z.string().optional(),
          inputTwo: z.string().optional()
        }).superRefine((data, ctx) => {
          if (selectOption === "A" && !data.inputOne?.trim()) {
            ctx.addIssue({/*...*/});
          }
          // Other conditions
        });
      
      // Usage:
      useForm({
        resolver: zodResolver(createSchema(currentSelection))
      });
      

      Choose based on whether the condition is part of your form state or external and handle the selectOption data, either include the condition in your form data or pass it externally.