Forms in React
22 minBeginnerReact
In this lesson
- ▸Make inputs controlled with
value+onChange - ▸Keep several fields in one state object and update them with a single handler
- ▸Handle
onSubmit, callevent.preventDefault(), and show validation messages
The big idea
In React, the form's data lives in state, not in the DOM. The input shows what state says (value) and tells state when it changes (onChange) — so your code always knows exactly what the user typed.
A plain HTML input keeps its own value inside the DOM. React prefers a single source of truth: the value lives in state, and the input just displays it. That's called a controlled input.
const [name, setName] = useState("");
<input value={name} onChange={(e) => setName(e.target.value)} />
<p>Hello, {name}!</p>Every keystroke: onChange → setName → re-render → the input shows the new value.
If you give an input value but no onChange, it becomes read-only — typing does nothing and React warns in the console. Controlled means both halves.
Many fields, one state object
Give each input a name that matches a key in your state object. Then one handler can update any field using a computed property name [name]. Remember: copy the object with ...form — never change state directly.
const [form, setForm] = useState({ name: "", email: "", track: "web" });
function handleChange(event) {
const { name, value } = event.target;
setForm({ ...form, [name]: value });
}
<input name="email" value={form.email} onChange={handleChange} />
<select name="track" value={form.track} onChange={handleChange}>…</select>Submitting
Put onSubmit on the <form> (not onClick on the button) so both clicking the button and pressing Enter work. By default a browser submit reloads the page, which would wipe your React state — so the first line is event.preventDefault().
function handleSubmit(event) {
event.preventDefault();
const errors = validate(form);
setErrors(errors);
if (Object.keys(errors).length > 0) return; // stop here
setSignups([...signups, { ...form, id: Date.now() }]);
setForm({ name: "", email: "", track: "web" }); // clear the form
}
<form onSubmit={handleSubmit} noValidate>…</form>In the WANCode preview, a form that forgets preventDefault() won't really reload — instead the console prints a note telling you. On a real site you'd lose everything typed.
Validation messages
Keep errors in state too, as an object like { email: "Needs an @" }. Then show each one with && right next to its field. Because the messages are strings, errors.email && <p>…</p> is safe — an empty result is undefined, which renders nothing.
We add noValidate to the form so the browser's built-in bubbles don't block our submit handler. Checking on the client is about helping the user — a real server must still validate everything again.
Create one state object form with name, email and track (start with track: "web"). Make all three fields controlled with value and a shared handleChange.
Then make the .preview name tag show what's typed, live: Name tag: {form.name} · {form.track}.
Checks
- Typing a name updates the name tag
- Choosing a track updates the name tag
- The email field is controlled too
Check understanding
What you learned
- ✓Controlled inputs:
value={state}+onChange→ state is the single source of truth. - ✓One object +
nameattributes +[name]: valuehandles many fields with one handler. - ✓Use
onSubmiton the form, start withevent.preventDefault(), validate, then save and clear. - ✓Errors are state too — render them with
errors.field && <p className="error">…</p>.
Console output from your code appears here.