Skip to content
WANCODE
BM

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, call event.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.

JSX
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.

JSX
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().

JSX
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

Q1

What makes an input "controlled"?

Q2

Why call event.preventDefault() in onSubmit?

Q3

setForm({ ...form, [name]: value }) — what does ...form do?

What you learned

  • Controlled inputs: value={state} + onChange → state is the single source of truth.
  • One object + name attributes + [name]: value handles many fields with one handler.
  • Use onSubmit on the form, start with event.preventDefault(), validate, then save and clear.
  • Errors are state too — render them with errors.field && <p className="error">…</p>.
Preview

Console output from your code appears here.

Step 1 of 3