Formularios Dinamicos con React Hook Form y Zod
Crea formularios dinamicos con campos condicionales, arrays de campos y validacion type-safe usando React Hook Form y Zod en Next.js.
Formularios Dinamicos con React Hook Form y Zod
Formularios dinamicos -- donde los campos cambian segun lo que el usuario selecciona o donde puedes agregar y quitar campos -- son de los patrones mas comunes en apps reales. React Hook Form con Zod te da la mejor combinacion de rendimiento y type-safety para resolverlos.
Si no has usado esta combinacion antes, arranca con la guia basica de validacion con Zod y React Hook Form. Aqui vamos directo a los patrones avanzados.
Setup rapido
npm install react-hook-form @hookform/resolvers zodCampos condicionales
El caso clasico: si el usuario selecciona "empresa", aparece el campo RFC. Si selecciona "persona", no.
"use client";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod";
const schema = z.discriminatedUnion("tipo", [
z.object({
tipo: z.literal("persona"),
nombre: z.string().min(2, "Nombre requerido"),
email: z.string().email("Email invalido"),
}),
z.object({
tipo: z.literal("empresa"),
nombre: z.string().min(2, "Nombre requerido"),
email: z.string().email("Email invalido"),
rfc: z.string().length(13, "RFC debe tener 13 caracteres"),
razonSocial: z.string().min(5, "Razon social requerida"),
}),
]);
type FormData = z.infer<typeof schema>;
export default function RegistroForm() {
const { register, handleSubmit, watch, formState: { errors } } = useForm<FormData>({
resolver: zodResolver(schema),
defaultValues: { tipo: "persona" },
});
const tipo = watch("tipo");
return (
<form onSubmit={handleSubmit((data) => console.log(data))}>
<select {...register("tipo")}>
<option value="persona">Persona</option>
<option value="empresa">Empresa</option>
</select>
<input {...register("nombre")} placeholder="Nombre" />
{errors.nombre && <span>{errors.nombre.message}</span>}
<input {...register("email")} placeholder="Email" />
{errors.email && <span>{errors.email.message}</span>}
{tipo === "empresa" && (
<>
<input {...register("rfc")} placeholder="RFC" />
{errors.rfc && <span>{errors.rfc.message}</span>}
<input {...register("razonSocial")} placeholder="Razon Social" />
{errors.razonSocial && <span>{errors.razonSocial.message}</span>}
</>
)}
<button type="submit">Registrar</button>
</form>
);
}discriminatedUnion en Zod valida automaticamente segun el valor de tipo. Si es "empresa", exige RFC y razon social. Si es "persona", no. Todo type-safe.
Arrays de campos (useFieldArray)
Para formularios donde el usuario agrega multiples items -- lineas de factura, experiencia laboral, ingredientes de una receta:
"use client";
import { useForm, useFieldArray } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod";
const schema = z.object({
cliente: z.string().min(2),
items: z.array(z.object({
descripcion: z.string().min(1, "Descripcion requerida"),
cantidad: z.number().min(1, "Minimo 1"),
precio: z.number().min(0, "Precio invalido"),
})).min(1, "Agrega al menos un item"),
});
type FormData = z.infer<typeof schema>;
export default function FacturaForm() {
const { register, control, handleSubmit, formState: { errors } } = useForm<FormData>({
resolver: zodResolver(schema),
defaultValues: {
items: [{ descripcion: "", cantidad: 1, precio: 0 }],
},
});
const { fields, append, remove } = useFieldArray({
control,
name: "items",
});
return (
<form onSubmit={handleSubmit((data) => console.log(data))}>
<input {...register("cliente")} placeholder="Cliente" />
{fields.map((field, index) => (
<div key={field.id} className="flex gap-2">
<input
{...register(`items.${index}.descripcion`)}
placeholder="Descripcion"
/>
<input
{...register(`items.${index}.cantidad`, { valueAsNumber: true })}
type="number"
placeholder="Cant"
/>
<input
{...register(`items.${index}.precio`, { valueAsNumber: true })}
type="number"
placeholder="Precio"
/>
{fields.length > 1 && (
<button type="button" onClick={() => remove(index)}>
Quitar
</button>
)}
</div>
))}
<button
type="button"
onClick={() => append({ descripcion: "", cantidad: 1, precio: 0 })}
>
Agregar item
</button>
<button type="submit">Crear factura</button>
</form>
);
}useFieldArray te da append, remove, move, swap e insert. El schema de Zod valida cada item del array automaticamente.
Validacion cruzada con superRefine
Cuando un campo depende del valor de otro:
const schema = z.object({
password: z.string().min(8),
confirmPassword: z.string(),
}).superRefine((data, ctx) => {
if (data.password !== data.confirmPassword) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: "Las contrasenas no coinciden",
path: ["confirmPassword"],
});
}
});Reutilizar el schema en el servidor
La ventaja de Zod es que el mismo schema funciona en cliente y servidor:
// lib/schemas/registro.ts -- un solo archivo
export const registroSchema = z.object({ /* ... */ });
// En el componente (cliente)
const form = useForm({ resolver: zodResolver(registroSchema) });
// En la Server Action (servidor)
export async function registrar(formData: FormData) {
const parsed = registroSchema.safeParse(Object.fromEntries(formData));
if (!parsed.success) return { errors: parsed.error.flatten() };
// ... guardar en DB
}Doble validacion, un solo schema, zero duplicacion. Si quieres profundizar en Zod, la guia de validacion con Zod cubre todo desde lo basico hasta patrones avanzados.
Preguntas frecuentes
Como agrego campos dinamicos con React Hook Form?
Con el hook useFieldArray. Le pasas el nombre del campo que es un array en tu schema, y te da funciones append, remove y fields para agregar, quitar y renderizar campos dinamicamente.
Como hago validacion condicional con Zod?
Con discriminatedUnion o con refine/superRefine. Si un campo depende del valor de otro (por ejemplo, si seleccionas 'empresa' debes llenar RFC), usas superRefine para validar condicionalmente.
React Hook Form o Formik en 2026?
React Hook Form. Es mas liviano, mas rapido, y se integra mejor con TypeScript y Zod. Formik esta en modo mantenimiento sin actualizaciones significativas desde 2023.
Puedo usar React Hook Form con Server Actions?
Si. Puedes validar con Zod en el cliente con React Hook Form, y re-validar en el servidor con el mismo schema de Zod en tu Server Action. Doble validacion con un solo schema.
Articulos relacionados
Zod Avanzado: Discriminated Unions, Transforms y Pipes
Patrones avanzados de Zod: discriminated unions, transforms, pipes, preprocess, y como validar datos complejos en TypeScript con schemas reutilizables.
tRPC + Next.js: APIs Type-Safe sin REST
Implementa tRPC en Next.js para APIs 100% type-safe. Sin schemas de API, sin fetch manual, sin types duplicados. End-to-end type safety con TypeScript.
Webhooks en Next.js: Recibe y Procesa Eventos
Implementa webhooks en Next.js para recibir eventos de Stripe, GitHub, Clerk y otros servicios. Verificacion de firmas, tipado y manejo de errores.