Aunque los componentes controlados son el enfoque recomendado para la mayoría de los escenarios de formularios en React, los componentes no controlados ofrecen una alternativa que puede ser más simple y eficiente en ciertas situaciones. Esta lección explora cuándo y cómo usar los componentes no controlados de manera efectiva.
Los componentes no controlados son elementos de formulario que mantienen su propio estado interno. En lugar de ser dirigidos por el estado de React, dependen del DOM para manejar los datos del formulario. Los valores se recuperan directamente del DOM cuando se necesitan, normalmente durante el envío del formulario.
La característica principal de los componentes no controlados es que React no "controla" los valores de los elementos del formulario durante el ciclo de vida del componente.
Los componentes no controlados pueden ser una buena elección cuando:
La herramienta principal para trabajar con componentes no controlados es el hook useRef
de React (o createRef
en componentes de clase).
1import React, { useRef } from "react"; 2 3function FormularioNoControlado() { 4 const refInputNombre = useRef(null); 5 const refInputEmail = useRef(null); 6 7 const manejarEnvio = (e) => { 8 e.preventDefault(); 9 const datosFormulario = { 10 nombre: refInputNombre.current.value, 11 email: refInputEmail.current.value 12 }; 13 console.log("Formulario enviado con datos:", datosFormulario); 14 // Procesar los datos del formulario 15 }; 16 17 return ( 18 <form onSubmit={manejarEnvio}> 19 <div> 20 <label htmlFor="nombre">Nombre:</label> 21 <input 22 type="text" 23 id="nombre" 24 ref={refInputNombre} 25 defaultValue="" // Valor inicial opcional 26 /> 27 </div> 28 29 <div> 30 <label htmlFor="email">Email:</label> 31 <input 32 type="email" 33 id="email" 34 ref={refInputEmail} 35 defaultValue="" 36 /> 37 </div> 38 39 <button type="submit">Enviar</button> 40 </form> 41 ); 42}
Puntos importantes a tener en cuenta:
ref
para crear una referencia al elemento DOMdefaultValue
en lugar de value
para el valor inicialref.current.value
cuando se necesitanonChange
1function EntradaTextoNoControlada() { 2 const refInput = useRef(null); 3 4 return <input type="text" ref={refInput} defaultValue="Texto predeterminado" />; 5}
1function CasillaVerificacionNoControlada() { 2 const refCasilla = useRef(null); 3 4 const estaMarcada = () => { 5 return refCasilla.current.checked; 6 }; 7 8 return ( 9 <> 10 <label> 11 <input 12 type="checkbox" 13 ref={refCasilla} 14 defaultChecked={false} 15 /> 16 Acepto los términos 17 </label> 18 <button onClick={() => alert(estaMarcada())}> 19 Comprobar estado 20 </button> 21 </> 22 ); 23}
Nota: Para casillas de verificación, usa defaultChecked
en lugar de defaultValue
.
1function SelectNoControlado() { 2 const refSelect = useRef(null); 3 4 const obtenerValorActual = () => { 5 return refSelect.current.value; 6 }; 7 8 return ( 9 <> 10 <select ref={refSelect} defaultValue="naranja"> 11 <option value="manzana">Manzana</option> 12 <option value="naranja">Naranja</option> 13 <option value="platano">Plátano</option> 14 </select> 15 <button onClick={() => alert(obtenerValorActual())}> 16 Obtener fruta seleccionada 17 </button> 18 </> 19 ); 20}
Las entradas de archivo son inherentemente no controladas en React, ya que su valor es de solo lectura:
1function EntradaArchivo() { 2 const refEntradaArchivo = useRef(null); 3 4 const manejarEnvio = (e) => { 5 e.preventDefault(); 6 const archivos = refEntradaArchivo.current.files; 7 // Procesar archivos 8 console.log(`Seleccionados ${archivos.length} archivos`); 9 }; 10 11 return ( 12 <form onSubmit={manejarEnvio}> 13 <input type="file" ref={refEntradaArchivo} multiple /> 14 <button type="submit">Subir</button> 15 </form> 16 ); 17}
Hay dos formas de especificar el valor inicial de un componente no controlado:
defaultValue
(o defaultChecked
para casillas de verificación y botones de radio)1// Enfoque recomendado de React 2<input type="text" defaultValue="Valor inicial" ref={refInput} /> 3 4// Enfoque HTML (no recomendado en React) 5<input type="text" value="Valor inicial" ref={refInput} />
Aquí hay un ejemplo más completo que demuestra un formulario no controlado con varios tipos de entrada:
1import React, { useRef } from "react"; 2 3function FormularioRegistro() { 4 const refFormulario = useRef(null); 5 6 const manejarEnvio = (e) => { 7 e.preventDefault(); 8 const formulario = refFormulario.current; 9 10 const datosFormulario = { 11 nombre: formulario.nombre.value, 12 apellido: formulario.apellido.value, 13 email: formulario.email.value, 14 genero: formulario.genero.value, 15 intereses: Array.from(formulario.intereses) 16 .filter(casilla => casilla.checked) 17 .map(casilla => casilla.value), 18 pais: formulario.pais.value, 19 comentarios: formulario.comentarios.value 20 }; 21 22 console.log("Formulario enviado con:", datosFormulario); 23 // Procesar los datos 24 }; 25 26 return ( 27 <form ref={refFormulario} onSubmit={manejarEnvio}> 28 <div> 29 <label htmlFor="nombre">Nombre:</label> 30 <input type="text" id="nombre" name="nombre" defaultValue="" /> 31 </div> 32 33 <div> 34 <label htmlFor="apellido">Apellido:</label> 35 <input type="text" id="apellido" name="apellido" defaultValue="" /> 36 </div> 37 38 <div> 39 <label htmlFor="email">Email:</label> 40 <input type="email" id="email" name="email" defaultValue="" /> 41 </div> 42 43 <div> 44 <p>Género:</p> 45 <label> 46 <input type="radio" name="genero" value="masculino" defaultChecked /> 47 Masculino 48 </label> 49 <label> 50 <input type="radio" name="genero" value="femenino" /> 51 Femenino 52 </label> 53 <label> 54 <input type="radio" name="genero" value="otro" /> 55 Otro 56 </label> 57 </div> 58 59 <div> 60 <p>Intereses:</p> 61 <label> 62 <input type="checkbox" name="intereses" value="deportes" /> 63 Deportes 64 </label> 65 <label> 66 <input type="checkbox" name="intereses" value="musica" /> 67 Música 68 </label> 69 <label> 70 <input type="checkbox" name="intereses" value="lectura" /> 71 Lectura 72 </label> 73 </div> 74 75 <div> 76 <label htmlFor="pais">País:</label> 77 <select id="pais" name="pais" defaultValue=""> 78 <option value="" disabled>Selecciona un país</option> 79 <option value="usa">Estados Unidos</option> 80 <option value="canada">Canadá</option> 81 <option value="mexico">México</option> 82 <option value="otro">Otro</option> 83 </select> 84 </div> 85 86 <div> 87 <label htmlFor="comentarios">Comentarios:</label> 88 <textarea 89 id="comentarios" 90 name="comentarios" 91 defaultValue="" 92 rows="4" 93 /> 94 </div> 95 96 <button type="submit">Registrarse</button> 97 </form> 98 ); 99}
En este ejemplo, usamos una única referencia para todo el formulario y accedemos a los campos individuales por sus atributos de nombre.
Aunque los componentes no controlados no ofrecen validación en tiempo real tan fácilmente como los componentes controlados, aún puedes implementar validación:
1function FormularioNoControladoValidado() { 2 const refEmail = useRef(null); 3 const [errorEmail, setErrorEmail] = useState(""); 4 5 const validarEmail = () => { 6 const email = refEmail.current.value; 7 const esValido = /\S+@\S+\.\S+/.test(email); 8 9 if (!esValido) { 10 setErrorEmail("Por favor ingresa una dirección de email válida"); 11 return false; 12 } else { 13 setErrorEmail(""); 14 return true; 15 } 16 }; 17 18 const manejarEnvio = (e) => { 19 e.preventDefault(); 20 if (validarEmail()) { 21 // Lógica de envío del formulario 22 console.log("Formulario enviado con:", refEmail.current.value); 23 } 24 }; 25 26 return ( 27 <form onSubmit={manejarEnvio}> 28 <div> 29 <label htmlFor="email">Email:</label> 30 <input 31 type="email" 32 id="email" 33 ref={refEmail} 34 onBlur={validarEmail} 35 /> 36 {errorEmail && <p className="error">{errorEmail}</p>} 37 </div> 38 39 <button type="submit">Enviar</button> 40 </form> 41 ); 42}
En este patrón, validamos la entrada cuando pierde el foco (onBlur
) o durante el envío del formulario.
Aquí hay una comparación rápida para ayudarte a decidir qué enfoque utilizar:
Característica | Controlado | No Controlado |
---|---|---|
Datos del formulario almacenados en | Estado de React | DOM |
Acceso inmediato al valor de entrada | Sí | No (requiere acceso a ref) |
Validación en tiempo real | Fácil | Más complejo |
Código requerido | Más | Menos |
Rendimiento con muchos campos | Puede ser más lento | Generalmente más rápido |
Reinicio del formulario | Fácil (reiniciar estado) | Requiere manipulación del DOM |
Integración con código no React | Más difícil | Más fácil |
required
, pattern
, etc.)Los componentes no controlados proporcionan una alternativa más simple y a veces más eficiente a los formularios controlados en React. Aunque ofrecen menos control sobre los datos del formulario durante el ciclo de vida del componente, requieren menos código y pueden ser más fáciles de implementar para formularios simples.
Para la mayoría de las aplicaciones React, los componentes controlados siguen siendo el enfoque recomendado debido a su previsibilidad e integración con el modelo de estado de React. Sin embargo, entender los componentes no controlados te da una herramienta adicional en tu kit de herramientas de React, especialmente útil para formularios simples, entradas de archivo o cuando se integra con código existente.