Forms
By the end of this lesson you'll be able to wire any input to React state, manage a whole form with one state object, handle submit without a page reload, and validate fields with clear error messages — the skills behind every login, signup, and checkout screen.
Learn Python, JavaScript, Java and more with free interactive lessons, real projects and AI-powered help. Beginner-friendly.
Part of the free React course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.
What You'll Learn in This Lesson
1️⃣ Controlled Inputs: value + onChange
A controlled input gets its displayed text from React state, and reports every keystroke back to state. Two props make this happen: value={name} tells the input what to show, and onChange runs on every keystroke so you can call setName . Miss either one and the input either won't update or won't be controlled. Here's the smallest complete example — read it first.
It's a loop on every keystroke: type → onChange → setState → re-render → the new value appears . State is never "behind" — it is what the box shows. The runnable box below walks through that round-trip one key at a time. Run it and watch state catch up.
2️⃣ One State Object for Many Fields
For a single box, one useState('') per field is fine. But a login form has an email and a password, and a separate useState for each gets noisy fast. The cleaner pattern is one object — {' '} — updated with a computed key . A computed key is the [name] in {' '} : the square brackets mean "use the value of name as the key", so one handler can update whichever field changed.
The golden rule: never mutate the old object . Always spread the previous values ( ...prev ) first, then override the one field that changed. Skip the spread and you wipe out every other field — run this to see exactly how that happens.
Now you try the core move — updating one field by name while keeping the rest. Fill in the blank, then run it.
3️⃣ Text, Checkbox, Select, and Radio
Most inputs bind to value , but a checkbox is the exception : it has no text, so you bind checked (a boolean) and read e.target.checked , not e.target.value . A puts value on the select element itself — not on each like raw HTML. Radio buttons share one name , and each one's checked is a comparison against the current state.
4️⃣ Handling Submit (preventDefault)
By default, submitting an HTML form reloads the page and throws away your React state — almost never what you want. Put onSubmit on the (not on the button), and the very first line of your handler should be e.preventDefault() . After that, the form data is just an object in state — validate it, send it to an API, whatever you need.
Here's the rest of that handler in plain JS: after preventDefault , you validate, and only send the data if there are no errors. Run it to see the full submit → validate → send flow.
5️⃣ Basic Validation
Validation is just a function that inspects your form object and returns an errors object — one key per broken rule, with a human-readable message. An empty {' '} means everything passed, which you check with Object.keys(errors).length === 0 . Keep this logic in a plain function so it's easy to test and reuse. Your turn: add the password rule.
📋 Quick Reference
Q: What exactly is the difference between controlled and uncontrolled?
A controlled input's value comes from React state ( value={x} ), so state is the single source of truth. An uncontrolled input keeps its value in the DOM, and you read it with a ref only when needed. Controlled is the default React style and the one you should learn first.
Q: Why must I spread ...prev when updating one field?
Setting state to {' '} replaces the whole object, so every other field becomes undefined . Spreading copies the existing fields first, then your one change overrides just that key.
A checkbox represents a yes/no, not text. Its value attribute doesn't change when you tick it, but checked flips between true and false — that's the boolean you want in state.
Q: Do I still need validation if I add HTML required ?
HTML attributes help, but they're easy to bypass and can't express rules like "passwords must match." Always validate in JavaScript too — and remember client-side checks are for UX; the server must validate again for security.
No blanks this time — just a brief and an outline. Build the state logic behind a signup form: a field-updater that keeps the other fields, and a validator for email, password, and the terms checkbox. Run it and check your output against the expected line in the comments.
Practice quiz
What two props make a text input controlled?
- defaultValue and onInput
- checked and onChange
- value and onChange
- ref and onSubmit
Answer: value and onChange. A controlled input binds value={state} and updates state in onChange on every keystroke.
When updating one field of a form state object, why must you spread ...prev first?
Setting state to { [name]: value } replaces the whole object; spread the old fields first to keep them.
What is a 'computed key' in { [name]: value }?
- A key React computes automatically
- A memoized value
- A unique list key
- The square brackets use the value of the variable name as the object key
Answer: The square brackets use the value of the variable name as the object key. [name] means 'use the value of name as the key', so one handler can update whichever field changed.
Which property do you bind and read for a checkbox?
- value and e.target.value
- checked and e.target.checked
- selected and e.target.selected
- on and e.target.on
Answer: checked and e.target.checked. A checkbox is yes/no, so bind checked={bool} and read e.target.checked (a boolean), not value.
For a <select>, where does the value prop go?
- On the <select> element itself
- On each <option>
- On the surrounding <form>
- On a hidden input
Answer: On the <select> element itself. In React, value goes on the <select> element, unlike raw HTML where the selected option is marked.
What is the first line of a typical onSubmit handler?
- return false
- e.stopPropagation()
- e.preventDefault()
- console.log(form)
Answer: e.preventDefault(). Call e.preventDefault() first to stop the browser reloading the page and wiping React state.
How should a validation function report problems?
- By throwing an error
- By returning an errors object — one key per broken rule, empty {} means valid
- By calling alert()
- By setting a global flag
Answer: By returning an errors object — one key per broken rule, empty {} means valid. Return an errors object; check Object.keys(errors).length === 0 to know the form is valid.
What causes React's 'value prop without an onChange handler' warning?
- Using defaultValue
- Too many fields
- Using a select
- An input has value but no onChange, making it read-only
Answer: An input has value but no onChange, making it read-only. A controlled input needs both value and onChange; with value alone React makes it read-only and warns.
Why initialize each form field to '' (or false) rather than leaving it undefined?
- It looks cleaner
- To avoid the 'changing an uncontrolled input to be controlled' warning
- It is required by JSX
- To speed up validation
Answer: To avoid the 'changing an uncontrolled input to be controlled' warning. Starting at undefined then becoming defined flips the input's mode; initialize to a defined value.
How do you check that an errors object means the form is valid?
- errors === null
- errors === false
- Object.keys(errors).length === 0
- !errors
Answer: Object.keys(errors).length === 0. An empty {} has zero keys, so Object.keys(errors).length === 0 indicates no validation errors.