Search code examples
reactjstypescriptreact-hook-formzod

zod conditional validation base on form field


I have a status = 'DRAFT' | 'READY' enum field in my form, is it possible to change validation in zod lib based on value of this field?

// validation passed
{
  name: "John",
  surname: null,
  status: "DRAFT"
}

// validation failed
{
  name: "John",
  surname: null,
  status: "READY"
}

So esentialy if status === "READY" remove .min(1) from here

const schema = z.object({
  name: z.string(),
  surname: z.string().min(1),
  status: z.enum(["READY", "DRAFT"])
});

Solution

  • The answer you came up with using unions is possible. An alternative that might yield better types in the future is to take advantage of zod's discriminatedUnion schema:

    // placing shared fields in one place to avoid repetition
    const base = z.object({
      name: z.string(),
    });
    
    const schema = z.discriminatedUnion(
      'status',
      [
        z.object({
          status: z.literal("DRAFT"),
          surname: z.string(),
        }).merge(base),
        z.object({
          status: z.literal("READY"),
          surname: z.string().min(1),
        }).merge(base),
      ],
    );
    

    In this case I believe you'll end up with the same inferred types as you would using union directly, however, this approach is slightly more resilient to other changes you might want to discriminate between. For example if the DRAFT object allowed surname to be optional then using the discriminated union would me that you could refine to non-optional types if status is "READY".

    With union definition, the type for surname in that example would always be string | undefined regardless of the value of status.