Entradas Controladas vs No Controladas
En React 19, no necesitas escribir entradas completamente controladas manualmente para la mayoría de campos nativos. Pero los patrones controlados (especialmente a través de Controller) siguen siendo recomendados cuando trabajas con bibliotecas UI modernas o formularios interactivos ricos.
- React Hook Form abraza componentes no controlados pero es completamente compatible con componentes controlados.
- La mayoría de bibliotecas UI están construidas solo para modo controlado, por lo que mezclar es la forma estándar de obtener lo mejor de ambos mundos:
- Rendimiento de campos nativos no controlados
- Flexibilidad de componentes de biblioteca UI controlados
¿Cuál es la diferencia?
Controlada — El estado de React es la única fuente de verdad. Cada pulsación de tecla actualiza el estado y re-renderiza.
function ControlledInput() {
const [email, setEmail] = useState("");
return (
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
/>
);
}No controlada — El DOM sostiene el valor. Lo lees al enviar (a través de ref o FormData).
function UncontrolledInput() {
const ref = useRef<HTMLInputElement>(null);
function handleSubmit(e: React.FormEvent) {
e.preventDefault();
console.log(ref.current?.value);
}
return (
<form onSubmit={handleSubmit}>
<input type="email" ref={ref} defaultValue="" />
<button type="submit">Enviar</button>
</form>
);
}No controlada con acciones del servidor de React 19
El estándar moderno para formularios simples — sin estado, sin refs, sin re-renderizados.
async function subscribe(formData: FormData) {
"use server";
const email = formData.get("email");
await saveSubscriber(email);
}
export function NewsletterForm() {
return (
<form action={subscribe}>
<input name="email" type="email" required />
<button type="submit">Suscribirse</button>
</form>
);
}react-hook-form: no controlada por defecto
register() conecta las entradas como no controladas — rápido, re-renderizados mínimos.
import { useForm } from "react-hook-form";
type FormValues = { email: string; password: string };
export function LoginForm() {
const { register, handleSubmit } = useForm<FormValues>();
return (
<form onSubmit={handleSubmit((data) => console.log(data))}>
<input {...register("email")} type="email" />
<input {...register("password")} type="password" />
<button type="submit">Inicia sesión</button>
</form>
);
}react-hook-form: Controller para bibliotecas UI
Usa Controller para conectar con componentes controlados como shadcn/ui Select o MUI.
import { Controller, useForm } from "react-hook-form";
import { Select, SelectTrigger, SelectContent, SelectItem } from "@/components/ui/select";
export function PlanForm() {
const { control, handleSubmit } = useForm({ defaultValues: { plan: "free" } });
return (
<form onSubmit={handleSubmit((data) => console.log(data))}>
<Controller
name="plan"
control={control}
render={({ field }) => (
<Select value={field.value} onValueChange={field.onChange}>
<SelectTrigger />
<SelectContent>
<SelectItem value="free">Gratis</SelectItem>
<SelectItem value="pro">Pro</SelectItem>
</SelectContent>
</Select>
)}
/>
<button type="submit">Guardar</button>
</form>
);
}Cuándo usar cuál
- La mayoría de
<input>,<textarea>,<select>nativos → no controladas conregister()o acciones del servidor - Componentes personalizados o de terceros (shadcn/ui, MUI, etc.) → controladas con
<Controller> - Necesitas lógica en tiempo real en algunos campos, rendimiento en otros → mezcla según sea necesario
Reglas importantes para evitar errores
- Nunca cambies una sola entrada de controlada a no controlada (o viceversa) durante su tiempo de vida — esto desencadena advertencias de React.
- No añadas manualmente
value+onChangea un campo que usaregister()— rompe el patrón no controlado. - Para el mismo nombre de campo, mantén un enfoque consistente.
- Siempre proporciona
defaultValue(novalue) para entradas no controladas para evitar la advertencia controlada/no controlada.
Guía de Decisión de React 19 / Next.js de 2026
Entradas No Controladas (enfoque nativo de React 19) – Usa esto cuando la mayoría de estos sean verdad:
- El formulario es simple (inicio de sesión, contacto, boletín, configuración básica — usualmente menos de 8 campos)
- Principalmente necesitas los valores solo al enviar
- Estás usando acciones del servidor con
<form action={...}> - Quieres re-renderizados mínimos y el mejor rendimiento posible
- Planeas usar
useActionStateyuseFormStatus - La mejora progresiva importa (el formulario debe funcionar si JavaScript falla)
- Estás bien confiando principalmente en validación HTML5 más validación del lado del servidor
- Quieres el código más simple con reinicio automático del formulario en envío exitoso
- No hay actualizaciones de UI complejas en tiempo real ni campos interdependientes
- Prefieres menos repetición y un tamaño de bundle más pequeño
Entradas Controladas o react-hook-form – Usa esto cuando la mayoría de estos sean verdad:
- El formulario tiene muchos campos (más de 8–10) o es multi-paso
- Necesitas validación en tiempo real, formato o retroalimentación en vivo mientras el usuario escribe
- Los campos dependen entre sí (mostrar/ocultar, habilitar/deshabilitar o realizar cálculos)
- Necesitas pre-poblar el formulario con datos de una API
- Quieres mensajes de error del lado del cliente instantáneos con UX excelente
- Tienes lógica compleja del lado del cliente antes del envío (fortaleza de contraseña, totales, vistas previas en vivo)
- Estás usando TypeScript y quieres seguridad de tipo fuerte en todo el formulario
- Necesitas control fino sobre las actualizaciones de UI
- El formulario incluye cargas de archivos con vista previa o indicadores de progreso
- Quieres mantener la mayoría de la lógica en el cliente antes de enviar datos al servidor
Guía de Decisión Rápida (Mejor Práctica de 2026)
- Formulario simple + Acciones del Servidor → Entradas no controladas con
useActionStateyuseFormStatus - Complejo, multi-paso o UX rico → react-hook-form + zod (aún usa entradas no controladas internamente para rendimiento)
- Editando datos existentes → Usualmente react-hook-form con
reset()orequestFormResetnativo - Rendimiento máximo y código mínimo → Entradas no controladas nativas con hooks de React 19
- Interactividad muy pesada en tiempo real → react-hook-form para el mejor balance de control y rendimiento
Recomendación Predeterminada en 2026
Comienza con entradas no controladas + hooks nativos de React 19 para cualquier formulario nuevo simple o de tamaño medio. Cambia a react-hook-form en el momento en que necesites validación en tiempo real, campos condicionales o lógica compleja del lado del cliente.
Incluso cuando uses react-hook-form, todavía se beneficia del rendimiento de entrada no controlada bajo el capó.