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"
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.
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",
});
}
}
});
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.