React Forms Handling
Build and manage controlled forms in React with inputs, validation, and submit handlers.
TL;DR
- 01Create controlled inputs by storing value in state.
- 02Update state on every keystroke with onChange handlers.
- 03Validate and submit forms with a form submit event.
Tips
- 01Use the <code>name</code> attribute on inputs to build a generic change handler that works for any number of fields without repetition.
Warnings
- 01Always call <code>e.preventDefault()</code> in the submit handler to stop the browser from reloading the page.
Controlled Inputs
- Create a controlled input by storing the value in state.
const [email, setEmail] = useState(""); <input value={email} onChange={(e) => setEmail(e.target.value)} /> - The input value stays in sync with the state at all times.
- React is the single source of truth for the input's value.
- Every keystroke updates state, which updates the input display.
- Controlled inputs let you validate and transform input before storing.
Multiple Inputs
- Store form data in a single state object to keep it organized.
const [form, setForm] = useState({ name: "", email: "" }); const handleChange = (e) => { const { name, value } = e.target; setForm(prev => ({ ...prev, [name]: value })); }; - Use the
nameattribute to identify which input changed. - Spread the previous state and update only the changed field.
- This pattern scales well as forms grow with more fields.
- Consider moving to a form library for very large forms.
Checkboxes and Selects
- For checkboxes, use
checkedinstead ofvaluein state.const [agree, setAgree] = useState(false); <input type="checkbox" checked={agree} onChange={(e) => setAgree(e.target.checked)} /> - For select dropdowns, store the selected value in state.
const [role, setRole] = useState("user"); <select value={role} onChange={(e) => setRole(e.target.value)}> <option value="user">User</option> <option value="admin">Admin</option> </select> - Handle radio buttons like checkboxes but with a group of options.
- Textareas work like text inputs with the
valueattribute.
Validation
- Validate input on every change or only when the user leaves the field.
const [email, setEmail] = useState(""); const [error, setError] = useState(""); const handleChange = (e) => { const value = e.target.value; setEmail(value); if (!value.includes("@")) setError("Invalid email"); else setError(""); }; - Show validation errors near the invalid input for quick feedback.
- Disable the submit button while there are validation errors.
<button disabled={error}>Submit</button> - Use a form library like React Hook Form for complex validation rules.
Form Submission
- Prevent the default form submission and handle it with JavaScript.
const handleSubmit = (e) => { e.preventDefault(); console.log(form); }; <form onSubmit={handleSubmit}> {/* inputs here */} <button type="submit">Submit</button> </form> - Access form data from state and send it to an API.
- Show loading and success states while submitting.
- Reset the form after successful submission using
setForm({}). - Handle errors gracefully and display them to the user.
FAQ
Add a name attribute to each input that matches the corresponding state key, then use a single handler: setState(prev => ({ ...prev, [e.target.name]: e.target.value })). This one function scales to any number of fields.
You're missing e.preventDefault() at the top of your submit handler. Without it, the browser performs its default form submission, which reloads the page and wipes your component state.
Checkboxes use e.target.checked instead of e.target.value. In a shared handler, check e.target.type === 'checkbox' and use the appropriate property: type === 'checkbox' ? e.target.checked : e.target.value.
Validate on submit for simple forms to avoid showing errors before the user finishes typing. For better UX, validate on onBlur (when the field loses focus) so errors appear after the user leaves a field rather than mid-input.
A controlled input stores its value in React state and reads it back via the value prop, making React the single source of truth. An uncontrolled input stores its value in the DOM and is read via a ref; controlled inputs are preferred because they make validation and dynamic updates straightforward.