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.