Menu

Componentes controlados vs no controlados en React

Un input controlado recibe su valor del estado de React y solo cambia mediante onChange. Un input no controlado guarda su propio valor en el DOM, y lo lees con una ref o con FormData. Aprende la diferencia, los avisos comunes y cuándo usar cada uno.

Esta página incluye editores ejecutables: edita, ejecuta y ve el resultado al instante.

Un componente controlado es un elemento de formulario cuyo valor vive en el estado de React: pasas value y actualizas el estado en onChange, así que React decide qué muestra el input. Un componente no controlado guarda su valor en el DOM, como en HTML puro: pasas un defaultValue inicial y lees el valor actual después mediante una ref o FormData.

Escribe en ambos inputs. El párrafo sigue al primero en cada pulsación, porque cada cambio actualiza el estado y vuelve a renderizar. El segundo input cambia solo en el DOM; React no renderiza, y solo ves su valor cuando "Read both" lo pide.

Inputs controlados

Con un input controlado, el estado de React es la única fuente de verdad. El flujo en cada pulsación es:

  1. El usuario escribe y el navegador dispara un evento de input.
  2. Tu manejador onChange lee e.target.value y llama al setter.
  3. React vuelve a renderizar y escribe el nuevo estado en el value del input.

Como tu código está en medio de ese ciclo, puedes hacer cosas que un input no controlado no puede: rechazar o transformar caracteres, mostrar el valor en otro lugar, deshabilitar un botón hasta que el valor sea válido, o definir el valor desde el código (un botón "Clear", una sugerencia en la que el usuario hace clic).

Prueba a escribir letras: nunca aparecen, porque el manejador quita todo lo que no sea un dígito antes de actualizar el estado. El botón Verify sigue deshabilitado hasta que haya exactamente seis dígitos.

Controlado es lo que usan los ejemplos de la página de formularios, y es la opción por defecto en la mayor parte del código de React.

Inputs no controlados

Un input no controlado funciona como en HTML puro. React lo renderiza una vez con defaultValue (o defaultChecked para casillas y radios) y luego lo deja en paz. Para leer el valor, guarda una ref al elemento o lee todos los campos con nombre del formulario al enviar con FormData.

No hay estado para los campos ni onChange. Los valores se recogen solo cuando el formulario se envía. Desmarca la casilla y envía otra vez: la clave subscribe desaparece, porque una casilla sin marcar no forma parte de FormData en absoluto.

Cambiar defaultValue después del primer renderizado no le hace nada al input, porque React solo lo aplica cuando se crea el elemento. Para reiniciar un formulario no controlado, llama a e.target.reset(), o dale al formulario una key nueva para que React cree inputs nuevos.

Un input en el que no puedes escribir

Si pasas value pero no onChange, el input es controlado, pero nada actualiza nunca el estado. React vuelve a escribir el mismo valor después de cada pulsación, así que el input parece congelado.

Prueba a escribir en cada campo. El primero no cambia. El segundo es la solución para un input controlado, y mientras escribes ahí, el primer y el último campo lo siguen, porque los tres leen el mismo estado. El tercero acepta lo que escribes porque es no controlado. El cuarto dice a propósito que no se puede editar.

En desarrollo, React registra un aviso para el primer input: "You provided a value prop to a form field without an onChange handler". La vista previa funciona como un build de producción, así que aquí no verás ese aviso, pero sí en tu propio servidor de desarrollo.

Pasar de no controlado a controlado

React decide si un input es controlado mirando value. Si value es undefined o null, el input es no controlado. Así que un input cuyo value empieza como undefined y luego pasa a ser una cadena cambia de no controlado a controlado durante su vida, y React avisa en desarrollo: "A component is changing an uncontrolled input to be controlled".

El aviso señala un error real. Volver en la otra dirección, de una cadena a undefined, hace que el input vuelva a ser no controlado, y React deja de escribir en él.

Escribe algo en ambos inputs y pulsa cada botón Clear. El segundo input se vacía. El primero conserva su texto: poner el estado en undefined lo volvió no controlado, así que React ya no controla lo que muestra.

La fuente habitual de undefined son datos que aún no han cargado (value={user.name} antes de que llegue el usuario) o un campo de objeto que todavía no existe. Dos soluciones:

const [name, setName] = useState('');   // start with a string
<input value={user?.name ?? ''} />       // or fall back to one

Para las casillas la misma regla se aplica a checked: empiézalo en false, no en undefined.

Los inputs de archivo siempre son no controlados

Un <input type="file"> no puede ser controlado, porque los navegadores no permiten que una página defina su valor (una página que pudiera elegir archivos de tu disco sería un agujero de seguridad). Lee los archivos elegidos desde e.target.files en onChange, o mediante una ref cuando se envía el formulario.

Elige uno o dos archivos de tu computadora; no se sube nada, el ejemplo solo lista nombres y tamaños. El único valor que puedes asignar a un input de archivo es la cadena vacía, que es como el botón Clear lo reinicia.

Cuándo elegir cuál

ControladoNo controlado
El valor vive enEl estado de ReactEl DOM
Definir el valor inicialvalue / checkeddefaultValue / defaultChecked
Leer el valorEn cualquier momento, desde el estadoCon una ref, o FormData al enviar
Renderiza en cada pulsaciónSíNo
Cambiar el valor desde el códigoActualizar el estadoEscribir en el DOM mediante una ref
Útil paraValidación en vivo, formato, campos dependientesFormularios simples leídos al enviar, inputs de archivo, acciones de formulario

Elige controlado cuando la pantalla tiene que reaccionar al valor mientras el usuario escribe. Elige no controlado cuando solo necesitas los valores al final, que es también como funcionan las acciones de formulario de React 19: <form action={fn}> le entrega a tu función un FormData y después reinicia los campos no controlados.

Renderizar en cada pulsación suena costoso, pero en un formulario normal no es un problema. Solo lo es cuando un árbol grande y lento se vuelve a renderizar con el input; en ese caso, mantén el estado cerca del input o mueve la parte lenta a su propio componente.

Las mismas dos palabras describen también tus propios componentes. Un componente que recibe su valor y un onChange de su padre mediante props está controlado por el padre; uno que guarda su propio estado es no controlado. Mover el estado hacia arriba para hacer un componente controlado es de lo que trata elevar el estado.

Preguntas frecuentes

¿Qué es un componente controlado en React?

Un elemento de formulario cuyo valor actual viene del estado de React, definido con value (o checked) y actualizado mediante onChange. El estado de React es la única fuente de verdad, así que siempre conoces el valor y puedes cambiarlo desde el código.

¿Qué es un componente no controlado en React?

Un elemento de formulario que guarda su propio valor en el DOM, como en HTML puro. Le das un valor inicial con defaultValue (o defaultChecked) y lees el valor actual cuando lo necesitas, mediante una ref o FormData al enviar.

¿Por qué no puedo escribir en mi input de React?

Pasaste value sin un onChange. El input es controlado, así que React vuelve a poner el valor del estado después de cada pulsación. Agrega un onChange que actualice el estado, cambia a defaultValue, o agrega readOnly si de verdad no debe cambiar.

¿Cómo arreglo "A component is changing an uncontrolled input to be controlled"?

El value del input empezó como undefined (o null) y luego pasó a ser una cadena. Asegúrate de que siempre sea una cadena: inicia el estado en '', o escribe value={name ?? ''}.

¿Debo usar inputs controlados o no controlados?

Usa inputs controlados cuando la UI reacciona al valor mientras el usuario escribe: validación, formato, un botón deshabilitado, campos que dependen entre sí. Usa inputs no controlados cuando solo necesitas los valores al enviar, y para los inputs de archivo, que siempre son no controlados.

Ilustración de los lenguajes de programación de Coddy

Aprende a programar con Coddy

COMENZAR