State & Core HooksExtra· 35 min read

Multiple Inputs, Checkboxes & Selects

Manage a whole form’s worth of fields neatly with a single state object.

What you will learn

  • Manage many fields with one state object
  • Handle checkboxes and selects
  • Update nested state with spread

One state object for the whole form

Instead of separate state for each field, hold the form in one object and update it with the spread operator.

One handler for every field
function App() {
  const [form, setForm] = useState({ name: "", course: "html", agree: false });

  function update(e) {
    const { name, value, type, checked } = e.target;
    setForm({ ...form, [name]: type === "checkbox" ? checked : value });
  }

  return (
    <form>
      <input name="name" value={form.name} onChange={update} placeholder="Name" /><br/><br/>
      <select name="course" value={form.course} onChange={update}>
        <option value="html">HTML</option>
        <option value="react">React</option>
      </select><br/><br/>
      <label><input name="agree" type="checkbox" checked={form.agree} onChange={update} /> I agree</label>
      <pre>{JSON.stringify(form, null, 2)}</pre>
    </form>
  );
}
Live preview

Instead of one state per field, the whole form lives in one object: { name, course, agree }. Every input shares the same update handler. Inside it, e.target tells us which input fired and its name, value, type and checked. We build a new object with { ...form, [name]: ... }: spread copies all current fields, then [name] overwrites just the one that changed. For a checkbox we save checked (true/false); for everything else we save value. The <pre> at the bottom prints the live state so you can watch it change.

Here is exactly what happens, step by step, when you change any field:

  1. One object holds the whole form: const [form, setForm] = useState({ name: "", course: "html", agree: false }) — one piece of state for every field.
  2. Each input is wired to that object: it reads its value from state (value={form.name}, checked={form.agree}) and points its onChange at the same shared update function. Each input also has a name attribute ("name", "course", "agree") that says which field it controls.
  3. On any change, update runs. It reads const { name, value, type, checked } = e.target to learn which input fired (name), what was typed or picked (value), and — for a checkbox — whether it is ticked (checked).
  4. It builds a brand-new object: setForm({ ...form, [name]: type === "checkbox" ? checked : value }). The spread ...form copies every current field, and the computed key [name] overwrites just the one that changed — saving checked for a checkbox, or value for a text box or dropdown.
  5. React re-renders, so the matching input and the live <pre> readout both update instantly.

Note: Output: A name box, a course dropdown, an “I agree” checkbox, and below them the live form object, e.g.: { "name": "Asha", "course": "react", "agree": true } Editing any field updates the matching line instantly.

Tip: The trick is the computed property name [name]: value — it uses each input’s name attribute as the object key, so one handler updates any field.

Q. What does { ...form, [name]: value } do?

Answer: Spread copies all existing fields, then [name]: value overrides the one that changed — updating state immutably.

✍️ Practice

  1. Build a form with three fields managed by one state object.
  2. Add a checkbox and a select to the same single-object form.

🏠 Homework

  1. Build a registration form (name, email, course select, terms checkbox) using one state object.
Want to learn this with a mentor?

CodingClave runs guided, project-based training (28-day, 45-day & 6-month batches).

Explore Training →