I recently had to refactor a React Form when working with multiple inputs and I thought I'd share how I did it.
This is how my form looks like:
The Problem
- Look at the starter code below
- This
Formcomponent has 5 input fields in total; 5 different states and 5 differentonChangeinline functions - This is not exactly DRY code 🙀
import React, { useState } from "react";
export default function Form() {
const [newCompany, setCompany] = useState("");
const [newPosition, setPosition] = useState("");
const [newLink, setNewLink] = useState("");
const [newDate, setNewDate] = useState("");
const [newNote, setNewNote] = useState("");
return (
<form>
<input
value={newCompany}
onChange={(e) => setCompany(e.target.value)}
label="Company"
/>
<input
value={newPosition}
onChange={(e) => setPosition(e.target.value)}
label="Job Title"
/>
<input
value={newLink}
onChange={(e) => setNewLink(e.target.value)}
label="Job Link"
/>
<input
type="date"
value={newDate}
onChange={(e) => setNewDate(e.target.value)}
/>
<input
value={newNote}
onChange={(e) => setNewNote(e.target.value)}
label="Note"
/>
<button type="submit"> Submit </button>
</form>
);
}
- Also, if I want to add a
resetfunction later, my code will look like this: 🙅🏽♀️
const reset = () => {
setCompany("");
setPosition("");
setNewLink("");
setNewDate("");
setNewNote("");
};
The Solution: Refactoring ✨
Step 1: Add input default values and initialize state
- First, let's add default values to ALL input fields
- How do we do that? We create an object literal with those values and set to empty string
- Then, with the
useState()React Hook we initialize ourvaluesstate with theinitialValuesobject -
Important: Remember to add the
valueattribute to every input field with its corresponding value (e.g.values={values.company})
const initialValues = {
company: "",
position: "",
link: "",
date: "",
note: "",
};
export default function Form() {
const [values, setValues] = useState(initialValues);
return (
<form>
<input
value={values.company}
onChange={(e) => setCompany(e.target.value)}
label="Company"
/>
//...
Step 2: Handle multiple input change
- The goal here is to handle ALL inputs with a single
onChangehandler - In order to update and keep track of our input fields every time they change, we need to create a
handleInputChangefunction (see below) - What's happening here? (quick recap)
- First, we're using object destructuring to get or extract the
nameand thevalueattributes from our inputs (look at the the comments below - they're equivalent) - Then, we're updating our
valuesstate object with the existing values by using thesetValues()function and the spread operator - And finally, we're updating the
valueof the event that was triggered by thatonChangewith this ES6 syntax:[name]: value - This is a very important step!
We need to add a
nameattribute to our inputs and[name]: valuehere means that we're setting a dynamicnameproperty key (taken from our inputs - e.g.company: e.target.value) which will be equal to thevalueof our current input state.
- First, we're using object destructuring to get or extract the
Reference: React Docs
//...
const handleInputChange = (e) => {
//const name = e.target.name
//const value = e.target.value
const { name, value } = e.target;
setValues({
...values,
[name]: value,
});
};
return (
<form>
<input
value={values.company}
onChange={handleInputChange}
name="company" //IMPORTANT
label="Company"
/>
// ...
Step 3: Add handleInputChange to input fields
- Add the
handleInputChangefunction to theonChangeprop of every input field - Look at the final code; this is a much cleaner and manageable form 👌🏽
import React, { useState } from "react";
const initialValues = {
company: "",
position: "",
link: "",
date: "",
note: "",
};
export default function Form() {
const [values, setValues] = useState(initialValues);
const handleInputChange = (e) => {
const { name, value } = e.target;
setValues({
...values,
[name]: value,
});
};
return (
<form>
<input
value={values.company}
onChange={handleInputChange}
name="company"
label="Company"
/>
<input
value={values.position}
onChange={handleInputChange}
name="position"
label="Job Title"
/>
// ... Rest of the input fields
<button type="submit"> Submit </button>
</form>
);
}
I hope it was useful. All comments and feedback are welcome! 🎊

Top comments (23)
Hi this method works fine for multiple input but just with normal input fields. What if their are some booleans and arrays??? E.g In vue.js I have the following data model which change at some point in time, which in that case two way data binding make it way easier for us.
Thank you so much!! I'm learning about controlled forms, and this has been incredibly helpful!
Amazing perofrmance. Very thanks!
very good , but I don't like it. Now you have to maintain the names of the inputs, which is a string. It's a bug waiting to happen.
I suggest using this solution: stackoverflow.com/a/47707719/1079002
btw, do you have solution for nested objects? I need that, thanks!
Hi, just wanted to ask ,
since we are changing the state in handleInputChange
therefore, every time we will type in something the whole component will re-render since the state is changing
so isn't it an expensive approach in respect to performance?
This was so insightfull. But i have one quetion. how then would you reset the input fields upon form submission?. is it as simple as calling e.g setFormFields ( initialState )
Here my assumption is that initialState is an object e.g
const initialState={
someInput: ' ',
anotherInput: ' ',
}
Thank you
Really clean.
Amazing perofrmance. Very thanks!
Wow, that exactly what I was looking for
Amazing Deborah ! ☺️☺️