Forms & controlled inputs
Text, checkbox, select and radio inputs, controlled vs uncontrolled, validation and submission.
Forms are how users give your app information: sign-ups, searches, settings, checkouts, comments. React offers two approaches: controlled inputs, where React state drives every field, and uncontrolled inputs, where the DOM holds the values and you read them on submit. This lesson covers both, every common input type, validation and submission.
Controlled inputs#
A controlled input gets its value from state and reports every change through onChange:
The cycle on every keystroke: the user types → onChange fires → setName updates state → React re-renders → the input shows the new value. React state is the single source of truth.
That gives you full control:
If you set
valuewithoutonChange, the input is read-only and React warns you. UsereadOnlyif that's what you want, ordefaultValuefor an uncontrolled input.
Every input type#
Things to notice:
- One handler for many fields: each input has a
namematching a key in state, and the computed property[name]updates the right one. <textarea>and<select>usevaluein React (not inner text orselectedattributes).- Checkboxes use
checkedande.target.checked. - Radio buttons share a
name; each ischeckedwhen its value matches state. - Number inputs still give you strings — convert with
Number(form.age)when you use them, and keep""for "empty". - A
<label>wrapping the input (orhtmlFor+id) makes the label clickable and accessible.
Submitting a form#
Disable the button while sending to prevent double submissions, and always show success and error states. Built-in HTML validation (required, type="email", min, pattern) still works and runs before onSubmit.
Validation#
For custom rules, write a pure function that returns errors, and show them after the user has interacted with a field:
- Errors are derived from values on each render, so they're never stale.
touchedavoids shouting at users before they've typed anything.aria-invalidandaria-describedbymake errors accessible to screen readers.
For large forms, libraries like React Hook Form (often with Zod for schemas) handle this boilerplate efficiently. And always validate again on the server.
Uncontrolled inputs#
Not every form needs state for every keystroke. With uncontrolled inputs the DOM keeps the values; you read them when the form is submitted, using FormData:
Use defaultValue / defaultChecked to set initial values without controlling them.
File inputs (
<input type="file">) are always uncontrolled — their value can only be set by the user. Reade.target.filesinonChange, or useFormDataon submit.
React 19 makes the uncontrolled FormData approach even nicer with form actions (<form action={fn}>) and useActionState — you'll learn them in the React 19 lesson.
Common mistakes#
- Initialising state as
undefinedornulland getting the controlled/uncontrolled warning — use"". - Setting
valuewithoutonChange. - Forgetting
e.preventDefault()inonSubmit, causing a page reload. - Treating number input values as numbers.
- Mutating the form object:
form.email = x; setForm(form). - Missing labels — placeholders aren't labels.
What's next#
You've finished the fundamentals! Next, go deeper into effects with useEffect and the component lifecycle.
Check your understanding
Quick quiz
1.What makes an input *controlled*?
2.Which prop do you use to control a checkbox?
3.React warns: *A component is changing an uncontrolled input to be controlled*. What's a common cause?
Finished reading?
Mark this lesson complete to track your progress.