React Multiple Inputs with One Handler
Table of Contents + −
In the last lesson you learned about React Checkboxes, so you can read one input and keep it in state. This lesson handles a whole form at once: all fields in one object, and one handler that updates the right field by itself.
🤔 Why one handler instead of many?
Picture a signup form with name, email, and password. Giving each field its own state and function adds up fast.
- That’s three
useStatecalls and three handlers for one form. Now imagine ten fields. - Every new field means more code, and all of it looks almost the same. So it’s boring and easy to get wrong.
- The cleaner idea is to hold the whole form in one object in state, then write one handler that works for any field.
Here’s the messy way first, so you can feel the pain. Each field needs its own everything.
// ❌ A separate state and handler for each field — repetitiveconst [name, setName] = useState("");const [email, setEmail] = useState("");const [password, setPassword] = useState("");
function handleName(e) { setName(e.target.value); }function handleEmail(e) { setEmail(e.target.value); }function handlePassword(e) { setPassword(e.target.value); }So three fields already gives you six pieces of code that all do basically the same thing. We can do much better.
🧩 Keep all fields in one object
The first move is to stop making one state per field. Put them all together in a single object.
- One
useStateholds the whole form, with a key for each field. - You read a field with dot notation, like
form.email. - Each key in the object will line up with the
nameattribute on its input. That link is what makes the single handler work.
Here we set up the signup form as one object in state, with an empty string for each field.
import { useState } from "react";
function SignupForm() { const [form, setForm] = useState({ name: "", email: "", password: "", });
return ( <form> <input name="name" value={form.name} /> <input name="email" value={form.email} /> <input name="password" value={form.password} /> </form> );}Notice each input has a name that matches a key in the object. name="email" matches form.email, and so on. That matching is the whole trick.
🪄 One handler with a computed property name
Now for the magic part. We write one handleChange, and it figures out which field to update on its own.
- The event gives us two things we need:
e.target.nametells us which input fired, ande.target.valueis the new text. - We wrap
[e.target.name]in square brackets inside the new object. That’s a computed property name, which means “use the value of this expression as the key”. - The spread
...formcopies all the other fields, so only the one field that changed gets replaced.
Here is the messy way versus the clean way side by side. One handler replaces all of them.
// ❌ One handler per field — too much repeated codefunction handleEmail(e) { setForm({ ...form, email: e.target.value }); }function handleName(e) { setForm({ ...form, name: e.target.value }); }
// ✅ One handler for every field, using the input's name as the keyfunction handleChange(e) { setForm({ ...form, [e.target.name]: e.target.value });}So [e.target.name] becomes whatever the input is called. Type in the email box and the key is email. Type in the password box and the key is password. Same one function, every time.
Square brackets make the key dynamic
Without the brackets, a plain { name: e.target.value } would always set a key literally called “name”. The square brackets in [e.target.name] tell JavaScript to evaluate the expression first and use the result as the key. That’s what lets one handler target any field.
📝 Wire it up to every input
Now connect that single handler to all three inputs. Every input gets onChange={handleChange} and a name that matches its key.
- Each input passes the same
handleChange. No more one function per field. - The
nameattribute on each input is what tells the handler which field to update. value={form.name}and friends keep each box tied to its slice of state.
This is the full signup form with one object in state and one shared handler.
import { useState } from "react";
function SignupForm() { const [form, setForm] = useState({ name: "", email: "", password: "", });
function handleChange(e) { setForm({ ...form, [e.target.name]: e.target.value }); }
return ( <form> <input name="name" value={form.name} onChange={handleChange} /> <input name="email" value={form.email} onChange={handleChange} /> <input name="password" type="password" value={form.password} onChange={handleChange} /> </form> );}See how clean that is? Adding a fourth field is now just one more input with its own name. You don’t touch the handler at all.
The name must match the key
The single handler only works because e.target.name matches a key in your state object. If an input has name="mail" but your state key is email, the handler will create a new mail key instead of updating email. Keep the input’s name and the object key spelled exactly the same.
☑️ Handle a checkbox in the same handler
A checkbox is a little different, but one small check lets the same handler deal with it too.
- A text box gives you its value in
e.target.value, but a checkbox gives you true or false ine.target.checked. - For a checkbox, you want
e.target.checked, which is true or false. - For everything else, you want
e.target.value, which is the text. - So you pick the right one based on
e.target.type, all inside the same handler.
Here we add a “remember me” checkbox and teach the handler to read the correct property for each input type.
const [form, setForm] = useState({ name: "", email: "", password: "", remember: false,});
function handleChange(e) { const value = e.target.type === "checkbox" ? e.target.checked : e.target.value; setForm({ ...form, [e.target.name]: value });}
// In the JSX:// <input name="remember" type="checkbox"// checked={form.remember} onChange={handleChange} />So the handler asks one question first: is this a checkbox? If yes, it stores true or false. If no, it stores the typed text. One handler, every input type covered.
✅ Best practices
A few small habits keep this pattern smooth as your forms grow.
- Always match the input’s
nameto the object key, spelled the same way. That link is the whole thing. - Put the spread
...formfirst, then[e.target.name]: value, so your new value wins over the copied one. - Use the
e.target.type === "checkbox"check when your form mixes text fields and checkboxes. - Give every field a starting value in the initial object, like
""for text andfalsefor a checkbox, so the inputs are controlled from the start.
Why we spread instead of editing
Just like with any object in state, you never change form directly. You build a brand new object with the spread, change one field, and pass that to setForm. React only re-renders when it gets a new object, so the spread is what makes the screen update.
🧩 What You’ve Learned
- ✅ Many fields in one form are cleaner as a single object in state, not one
useStateeach - ✅ Each input gets a
nameattribute that matches a key in that object - ✅ One
handleChangereadse.target.nameande.target.valueto know what changed - ✅ A computed property name
[e.target.name]uses the input’s name as the object key - ✅ The spread
...formkeeps every other field while you update the one that changed - ✅ A checkbox uses
e.target.checked, picked withe.target.type === "checkbox"in the same handler
Check Your Knowledge
Test what you learned. Pick an answer for each question, then click Check.
- 1
Why store all form fields in one object instead of separate useState calls?
Why: Holding the fields in one object means a single handler can update any of them using the input's name. Separate state and handlers per field just repeat the same code over and over.
- 2
What does the single handleChange read to know which field changed?
Why: e.target.name tells the handler which input fired, and e.target.value is what was typed. Together they let one handler update the right field.
- 3
Why are the square brackets needed in { ...form, [e.target.name]: e.target.value }?
Why: Square brackets make a computed property name. JavaScript evaluates e.target.name first and uses the result as the key, so one handler can target any field by its name.
- 4
How do you handle a checkbox inside the same shared handler?
Why: A checkbox carries its state in e.target.checked, not e.target.value. Checking e.target.type lets the same handler read the correct property for both text inputs and checkboxes.
🚀 What’s Next?
Now you can wire up a whole form with one object and one handler, checkboxes included. Next you’ll learn how to check what the user typed before you accept it, like making sure the email looks right and the password isn’t empty.