
Existen hoy en día dos formas de aproximarse a los inputs controlados.
Cada una de estas maneras trae consigo un par de beneficios que tú tendrás que decidir si son los que prefieres para ti.
Vamos, pues, a explorar las dos principales maneras de controlar un formulario. En esta learning
exploraremos la más clásica: onChange
¿Muchos estados o un solo estado?
Para controlar los datos que el usuario inserta en nuestros formularios, primero tenemos que crear un estado para cada input y después, manualmente, colocar un listener
en cada input para lograr pasar el value
del input a cada uno de los estados asignados.
Supongamos entonces que tenemos un formulario relativamente simple, el de un SignUp
:
export default function App() { return ( <form style={ { display:'flex', flexDirection:'column', maxWidth:320, marginInline:'auto', gap:8 }}> <label htmlFor="name"> Escribe tu nombre </label> <input name="name" /> <label htmlFor="email"> Escribe tu correo electrónico </label> <input name="email" type="email" /> <label htmlFor="password"> Escribe tu contraseña </label> <input name="password" type="password" /> <label htmlFor="confirm"> Repite tu contraseña </label> <input name="confirm" type="password" /> <button> Unirme </button> </form> ) }
Nuestro objetivo es tener un estado para cada input. Esto lo podemos conseguir si declaramos un useState
para cada uno de los inputs:
const [email,setEmail]=useState('');
Pero esto sería escribir muchas declaraciones y tendríamos que crear una función para cada actualización.
Mejor hagamos un estado que tenga la forma de un objeto y coloquemos ahí todas las llaves necesarias:
const [form,setForm]=useState({ name:'', email:'', password:'', confirm:'', });
De esta forma, nos ahorramos escritura y al mismo tiempo tenemos una sola fuente de verdad. Es momento de meter lo que el usuario escriba en cada input.
onChange
en cada input
Para lograr introducir el value
de cada input en nuestro estado en la llave correcta, necesitamos colocar una función en onChange
. Hay varias maneras de hacerlo, te voy a mostrar dos: una para entender qué sucede y otra que nos va a ahorrar código y trabajo:
<input name="name" onChange={(event)=>{ const {value} = event.target; setForm({...form, name:value}); }} />
Observa dos cosas, primero, ¿qué está pasando?, y después, qué podrías convertir en dinámico para no tener que escribir este callback
en cada input y mejor pasar una función genérica:
<input name="name" onChange={handleChange} />
Algo así, ¿recuerdas los handlers
de eventos?
Como te habrás dado cuenta, escribir en cada input es tedioso en sí mismo, pero podemos aligerar la tarea definiendo un handleChange
que sea capaz de asignar el value
de cada input a su correspondiente estado:
const handleChange = (event) => { // El target es el input correspondiente const {name,value} = event.target; // Guardamos en el estado correspondiente usando name setForm({...form,[name]:value}); // Recuerda que los ... son el spread operator // Su función aquí, es sacar las llaves del objeto form y colocarlas una por una en este nuevo objeto que se le entrega a setForm, esto seria algo similar: // const newForm = {...form, [name]:value}; // setForm(newForm); }
Ahora, con esta función dinámica que utiliza el name
del input, podemos colocarla en todos los inputs sin mayor trabajo.
<input name="email" type="email" onChange={handleChange} />
👀 ¡Ojo!, la función handleChange es muy directa, se puede simplificar en un
oneliner
:const hc=ev=>setForm(form=>({...form,[ev.target.name]:ev.target.value}))
observa también que se puede usar el callback que nos entrega setForm, lo cual es mejor.
Por último, debemos darle el value
a cada input
Para convertir nuestros inputs en verdaderamente controlados, es necesario entregarles el value
, asegurándonos de que sea el correspondiente:
<input value={form.name} name="name" onChange={handleChange} /> <input value={form.email} name="email" type="email" onChange={handleChange} /> <input value={form.password} name="password" type="password" onChange={handleChange} /> <input value={form.confirm} name="confirm" type="password" onChange={handleChange} />
Hemos colocado el prop
value
en cada uno de nuestros inputs
lo que los convierte en realmente controlados, pues ahora no pueden mostrar otra cosa que no sea el estado asignado, y solo puede cambiar por medio del onChange
. Asegúrate de siempre incluir este par: value
y onChange
en un input controlado.
Confirmando que lo hicimos bien
Para saber que todo está en orden y que ahora tenemos almacenados los datos que el usuario ingresa, en un estado, vamos a interceptar el evento submit
del formulario evitando que refresque la página (que es su acción por default) y usando el evento para mandar a consola nuestro objeto de datos.
return ( <form onSubmit={handleSubmit} ...
Colocamos el handler
en el onSubmit
del formulario e interceptamos:
const handleSubmit = (event) => { event.preventDefault(); console.log("form: ", form) }
Observa cómo recibimos el evento y usamos el método preventDefault
para evitar que la página se refresque
👀 ¡Ojo!, la página naturalmente busca actualizarse, pues busca comunicarse con el servidor web y entregar el formulario, pero en React, cuando trabajamos en un SPA (single page application) la comunicación con el servidor no sucede en primer plano con request-response, sucede en segundo plano, con fetch.
Si rellenas todos los campos y finalmente le damos
submit
con el botón, vemos en consola nuestro objeto con todos los datos correctamente asignados.
De esta forma tienes un formulario controlado, a forma de resumen es importante no olvidar:
- Cada input debe tener su llave
name
- Crear una función
handleChange
- Definir la actualización dinámica de cada llave en el objeto
state
- Asignar esta función a cada input
- Crear una función
handleSubmit
y evitar el default del evento - Asignarla al
form
- Ahora puedes hacer lo que prefieras con los datos 🎉
Links relacionados
Happy coding 🕹
Full-stack components | modo1: API route
Checa este otro Post
