React 19 de Cero a Experto / Formularios y validación
Validación con Zod: un esquema, dos mundos
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:
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.