Un formulario de React guarda el valor de cada campo en el estado: el input muestra value={state} y lo actualiza con onChange. Cuando el usuario envía, un manejador onSubmit llama a e.preventDefault() para evitar la recarga de la página y luego usa los valores que ya están en el estado.
Escribe un nombre y pulsa Enter o haz clic en el botón. Luego borra la línea e.preventDefault();: la vista previa muestra los campos que el navegador habría enviado en un envío nativo, que en una página real también la recargaría.
Un input cuyo valor viene del estado se llama input controlado. La alternativa, dejar que el DOM guarde el valor y leerlo después, se explica en componentes controlados vs no controlados.
Inputs de texto y textarea
Todos los campos de texto funcionan igual: value desde el estado, onChange lee e.target.value. Un <textarea> sigue el mismo patrón. En HTML su texto va entre las etiquetas, pero en React lo pasas como value.
Como tú defines el valor, puedes cambiar lo que escribió el usuario antes de que se muestre: el campo de título convierte cada letra en mayúscula. El contador de caracteres se actualiza en cada pulsación porque body es estado y cada cambio vuelve a renderizar.
Select
Un <select> recibe value en el propio elemento select, no selected en una opción. e.target.value es el value de la opción elegida.
Para un select múltiple, agrega multiple y pasa un array como value. Lee las opciones elegidas con Array.from(e.target.selectedOptions, (o) => o.value).
Casillas y botones de radio
Una casilla se controla con checked, no con value, y su manejador lee e.target.checked, que es true o false. Los botones de radio de un grupo comparten un name; cada uno está marcado cuando el estado es igual a su valor.
Leer e.target.value en una casilla es un error común: en una casilla sin atributo value devuelve la cadena "on" esté marcada o no.
Un objeto de estado para muchos campos
Un formulario con diez campos no necesita diez llamadas a useState y diez manejadores. Guarda un objeto, dale a cada input un name que coincida con una clave y escribe un solo manejador que use el nombre como clave calculada: [e.target.name].
El manejador revisa el tipo de input para que la casilla guarde un booleano y los demás guarden texto. Agrega un campo agregando una clave al objeto inicial y un input con el mismo name; el manejador no necesita cambios. La propagación conserva todos los demás campos (consulta actualizar arrays y objetos).
Enviar un formulario
Pon la lógica de envío en el onSubmit del formulario, no en el onClick del botón. onSubmit también se dispara cuando el usuario pulsa Enter en un campo de texto, algo que un manejador de clic no captaría. Un <button> dentro de un formulario es un botón de envío por defecto; dale type="button" a los demás botones para que no envíen.
Abre la consola, escribe una contraseña y pulsa Enter: el formulario registra el objeto completo. Hacer clic en "Clear" no registra nada, porque es type="button". Quita ese atributo y Clear también empieza a enviar.
Validación y mostrar errores
Una validación simple es una función que recibe los valores y devuelve un objeto de mensajes. Ejecútala al enviar, guarda el resultado en el estado y muestra cada mensaje bajo su campo. Deshabilita el botón de envío mientras una petición está en curso para que un doble clic no envíe el formulario dos veces.
Haz clic en Save con los campos vacíos para ver ambos mensajes, y luego rellénalos. saveProfile sustituye a una petición real: espera 800 milisegundos y se resuelve. El atributo noValidate desactiva las burbujas de validación propias del navegador para que solo se vean tus mensajes; quítalo y agrega required a un input para comparar.
Los errores también se pueden calcular durante el renderizado en lugar de guardarse, por ejemplo const emailError = form.email && !form.email.includes('@'), que muestra el mensaje mientras el usuario escribe. Elige el momento que mejor le vaya al formulario: al enviar es más tranquilo, mientras se escribe da una respuesta más rápida.
Acciones de formulario en React 19
React 19 agrega otra forma de manejar un formulario. Pasa una función a la prop action del formulario. React evita la recarga de la página por ti y llama a la función con un objeto FormData que contiene cada campo que tiene un name. Los campos no necesitan estado en absoluto: son no controlados, y React los vacía cuando termina la acción.
Escribe tu propio comentario y publícalo. La lista muestra tu texto, y el input vuelve a Nice docs: cuando la acción se resuelve, React reinicia los campos no controlados a su defaultValue. La acción puede ser async, y mientras se ejecuta React trata la actualización como una transición.
Para mostrar un estado pendiente o un error de la acción, envuélvela con useActionState, que devuelve el último resultado, una acción envuelta para el formulario y un indicador isPending. Un botón muy dentro del formulario también puede leer el estado pendiente con useFormStatus de react-dom:
import { useFormStatus } from 'react-dom';
function SubmitButton() {
const { pending } = useFormStatus();
return <button disabled={pending}>{pending ? 'Posting...' : 'Post'}</button>;
}
useFormStatus no se carga en los editores de aquí, así que aparece solo en este bloque estático. Debe llamarse desde un componente renderizado dentro del <form>, no en el componente que renderiza el formulario.
El estado controlado con onSubmit y las acciones de formulario con FormData funcionan ambos en React 19. Los campos controlados sirven para formularios que reaccionan mientras escribes (validación en vivo, un contador de caracteres, campos que dependen entre sí). Las acciones sirven para formularios donde solo necesitas los valores cuando el usuario envía.
Preguntas frecuentes
¿Cómo obtengo el valor de un input en React?
Guárdalo en el estado y actualízalo desde onChange: <input value={name} onChange={e => setName(e.target.value)} />. Así el texto actual siempre está en name. Para las casillas lee e.target.checked en lugar de e.target.value.
¿Cómo evito que un formulario recargue la página en React?
Llama a e.preventDefault() al inicio del manejador onSubmit. Sin eso, el navegador hace un envío de formulario normal, que navega y recarga la página.
¿Cómo manejo muchos inputs con un solo estado en React?
Guarda un objeto en el estado, dale a cada input un name que coincida con una clave y usa un solo manejador con una clave calculada: setForm({ ...form, [e.target.name]: e.target.value }).
¿Por qué un textarea usa value en lugar de children en React?
En HTML el texto de un textarea va entre sus etiquetas, pero en React lo pasas como value (o defaultValue), igual que en un input. Así todos los campos de texto de un formulario de React funcionan igual.
¿Qué son las acciones de formulario en React 19?
En React 19 puedes pasar una función a <form action={fn}>. React evita la recarga de la página, llama a la función con el FormData del formulario y reinicia los campos no controlados cuando termina. useActionState agrega encima un indicador de pendiente y un resultado.