How can I make a field required based in other field value with Zod?

114 views Asked by At

I'm trying to validate a form with Zod and react-hook-form, and I need to make some fields required based on the value of other field. Here is my schema:

const formSchema = z.object({
    cli_rut: z.string(),
    cli_persona_natural: z.enum(["SI", "NO"]),
    cli_nombres: z.string().optional(),
    cli_apellido_paterno: z.string().optional(),
    cli_apellido_materno: z.string().optional(),
    cli_razon_social: z.string().optional(),
    cli_estado: z.string().default("ACTIVO").optional(),
});

in this case I need to make required cli_nombres, cli_apellido_materno, and cli_apellido_paterno if cli_persona_natural = "SI", any ideas of how can i do it? Thanks

I'm expecting the fields to be required if cli_persona_natural is "SI"

1

There are 1 answers

1
Souperman On BEST ANSWER

You can use the superRefine method to specify additional constraints like this, or you could use discriminatedUnion in this case and end up with slightly stronger types.

Super Refine Approach:

const formSchema = z
  .object({
    cli_rut: z.string(),
    cli_persona_natural: z.enum(["SI", "NO"]),
    cli_nombres: z.string().optional(),
    cli_apellido_paterno: z.string().optional(),
    cli_apellido_materno: z.string().optional(),
    cli_razon_social: z.string().optional(),
    cli_estado: z.string().default("ACTIVO").optional(),
  })
  .superRefine((data, ctx) => {
    if (data.cli_persona_natural === "SI") {
      if (data.cli_nombres === undefined) {
        // I took a best guess at the messages but it's not my native language
        ctx.addIssue({
          code: z.ZodIssueCode.custom,
          message: "El campo nombres es requerido",
        });
      }
      if (data.cli_apellido_paterno === undefined) {
        ctx.addIssue({
          code: z.ZodIssueCode.custom,
          message: "El campo apellido paterno es requerido",
        });
      }
      if (data.cli_apellido_materno === undefined) {
        ctx.addIssue({
          code: z.ZodIssueCode.custom,
          message: "El campo apellido materno es requerido",
        });
      }
    }
  });

Discriminated Union Approach

import { z } from "zod";

const baseSchema = z.object({
  cli_rut: z.string(),
  cli_razon_social: z.string().optional(),
  cli_estado: z.string().default("ACTIVO").optional(),
});

const personaNaturalSchema = z.object({
  cli_persona_natural: z.literal("SI"),
  cli_nombres: z.string(),
  cli_apellido_paterno: z.string(),
  cli_apellido_materno: z.string(),
});

// Again no clue if this naming makes sense in practice
const personaJuridicaSchema = z.object({
  cli_persona_natural: z.literal("NO"),
  cli_nombres: z.string().optional(),
  cli_apellido_paterno: z.string().optional(),
  cli_apellido_materno: z.string().optional(),
});

const formSchema = z
  .discriminatedUnion("cli_persona_natural", [
    personaNaturalSchema,
    personaJuridicaSchema,
  ])
  .and(baseSchema);

console.log(
  formSchema.safeParse({
    cli_persona_natural: "SI",
    cli_rut: "12345678",
    cli_nombres: "John",
    cli_apellido_paterno: "Steve",
    cli_apellido_materno: "Tessa",
  })
); // success
console.log(
  formSchema.safeParse({
    cli_persona_natural: "NO",
    cli_rut: "12345678",
  })
); // success
console.log(
  formSchema.safeParse({
    cli_persona_natural: "SI",
    cli_rut: "12345678",
  })
); // failure

This approach has a benefit with the types. If you check the value of cli_persona_natural you will get type inference about whether or not the other fields are required.