CodeForge

React 19 de Cero a Experto / Formularios y validación

Validación con Zod: un esquema, dos mundos

Teoría22 min20 XP

Las reglas inline de register sirven para lo simple, pero la validación seria pide más: reglas reutilizables, mensajes claros, validación cruzada, y —lo mejor— UN esquema que sirva en el formulario Y en el servidor. Ahí entra ZOD, el mismo que dominaste en TypeScript (SC-03): defines la forma y las reglas de tus datos una vez, y de ese esquema sacas tanto la VALIDACIÓN en runtime como el TIPO de TypeScript. Hoy unes Zod con React Hook Form —la combinación estándar para formularios serios—.

Un molde que valida y tipa a la vez

El esquema y zodResolver

Defines el esquema con Zod y se lo pasas a useForm mediante zodResolver. RHF validará cada envío contra el esquema:

rhf-zod.tsx
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod";

// 1. EL ESQUEMA: la forma y las reglas de los datos, en un solo lugar (SC-03)
const esquema = z.object({
nombre: z.string().min(3, "Mínimo 3 caracteres"),
email: z.string().email("Email inválido"),
edad: z.coerce.number().int().min(18, "Debes ser mayor de edad"),
});

// 2. EL TIPO se DERIVA del esquema (no lo escribes aparte)
type Campos = z.infer<typeof esquema>;

function Registro() {
const { register, handleSubmit, formState: { errors } } = useForm<Campos>({
  resolver: zodResolver(esquema), // 3. RHF valida con el esquema
});

const onSubmit = (datos: Campos) => api.registrar(datos); // 'datos' ya validado y tipado

return (
  <form onSubmit={handleSubmit(onSubmit)}>
    <input {...register("nombre")} />
    {errors.nombre && <span>{errors.nombre.message}</span>}

    <input {...register("email")} />
    {errors.email && <span>{errors.email.message}</span>}

    <input {...register("edad")} />
    {errors.edad && <span>{errors.edad.message}</span>}

    <button>Registrarse</button>
  </form>
);
}

Tres piezas: el esquema de Zod con las reglas y sus mensajes; el type Campos = z.infer<typeof esquema> que DERIVA el tipo del esquema (una sola fuente de verdad, como en SC-03); y resolver: zodResolver(esquema) que le dice a RHF que valide con Zod. Ahora los register ya no llevan reglas inline —todas viven en el esquema— y los mensajes de error salen de ahí. Agregar un campo es tocar UN lugar (el esquema) y el tipo, la validación y los errores se actualizan juntos.

Por qué un esquema gana

¿Qué ventaja da usar un esquema de Zod (con zodResolver) frente a las reglas inline de register?

Mini-reto

Diseña un esquema de Zod para un formulario de nuevo gasto (en papel o en tu proyecto): 1) nombre string de mínimo 2 caracteres; 2) valor number (con z.coerce.number) mayor que 0; 3) categoria como z.enum(["comida", "transporte", "ocio"]); 4) deriva el tipo con z.infer y conéctalo a useForm con zodResolver. Bonus: agrega un .refine para una regla cruzada (por ejemplo, que si la categoría es "ocio" el valor no supere cierto límite). Nota qué se toca al añadir un campo.

Qué sigue

Ya validas con esquemas. Pero mostrar errores no basta: deben ser ACCESIBLES —anunciados a lectores de pantalla, asociados a su campo, con el foco llevado al primer error—. Un formulario inaccesible frustra a muchos usuarios. La próxima lección —con demo en vivo— hace tus errores de formulario accesibles de verdad.