Formularios con varios campos en un estado
Cuando un formulario tiene varios campos, puedes guardarlos en un objeto. Cada input utiliza un atributo name para identificar qué propiedad debe actualizarse.
Importante: event.target.value es texto incluso cuando el input tiene type="number". Debemos convertir la edad y decidir cómo representar un campo vacío.
1import { useState, type ChangeEvent, type FormEvent } from 'react'; 2 3interface DatosFormulario { 4 name: string; 5 surname: string; 6 email: string; 7 edad: number | ''; 8} 9 10type CampoTexto = 'name' | 'surname' | 'email'; 11 12export default function Ejem_19() { 13 const [form, setForm] = useState<DatosFormulario>({ 14 name: 'Juan', 15 surname: 'González', 16 email: '', 17 edad: 20, 18 }); 19 const [mensaje, setMensaje] = useState(''); 20 21 function handleChange(event: ChangeEvent<HTMLInputElement>) { 22 const { name, value } = event.target; 23 if (name === 'edad') { 24 const edad = value === '' ? '' : event.target.valueAsNumber; 25 if (edad !== '' && !Number.isFinite(edad)) return; 26 setForm((prev) => ({ ...prev, edad })); 27 return; 28 } 29 if (name === 'name' || name === 'surname' || name === 'email') { 30 const campo: CampoTexto = name; 31 setForm((prev) => ({ ...prev, [campo]: value })); 32 } 33 } 34 35 function handleSubmit(event: FormEvent<HTMLFormElement>) { 36 event.preventDefault(); 37 if (form.edad === '' || form.edad < 0) { 38 setMensaje('Introduce una edad válida.'); 39 return; 40 } 41 setMensaje(`Datos preparados para ${form.name}, edad ${form.edad}.`); 42 console.log('Formulario:', form); 43 } 44 45 return ( 46 <form onSubmit={handleSubmit}> 47 <label>Nombre: <input name="name" value={form.name} onChange={handleChange} required /></label> 48 <label>Apellidos: <input name="surname" value={form.surname} onChange={handleChange} required /></label> 49 <label>Email: <input name="email" type="email" value={form.email} onChange={handleChange} required /></label> 50 <label>Edad: <input name="edad" type="number" min="0" step="1" value={form.edad} onChange={handleChange} required /></label> 51 <button type="submit">Enviar</button> 52 <p role="status">{mensaje}</p> 53 </form> 54 ); 55}
Conservar el resto del objeto #
{ ...prev, [campo]: value } crea un objeto nuevo: copia los campos anteriores y sustituye el que tiene ese nombre. El setter de useState reemplaza el objeto completo; no mezcla propiedades automáticamente. No hagas form.name = value, porque estarías mutando el objeto existente.
name es la clave del campo; value es su contenido. Los corchetes de [campo] utilizan el valor de esa variable como nombre de propiedad.
Para la edad usamos number | '': un número cuando hay una edad y una cadena vacía mientras el usuario borra el campo. Esto evita convertir accidentalmente un vacío en cero o almacenar NaN.
Prueba tú #
Edita solo el apellido y comprueba que el nombre se conserva. Borra la edad, vuelve a escribirla y revisa en la consola que llega como número, no como cadena.
Cuestionario del tema #
Abre el cuestionario en las actividades de este tema para comprobar lo aprendido. Cada respuesta incluye una explicación.