[React] Manage Multiple Form Inputs with Single State Variable
Utilizing ES6 destructuring and dynamic object keys.
Reference: John Smilga and freeCodeCamp's Full React Course 2020
When creating an application with few inputs, setting separate state variables for each input field is not an issue, as seen below:
import React, { useState } from 'react'
const ControlledInputs = () => {
// Separate state variables (firstName, email) to store input.
const [firstName, setFirstName] = useState("");
const [email, setEmail] = useState("");
const [people, setPeople] = useState([]);
const handleSubmit = (event) => {
// prevents page from refreshing after submitting the form
event.preventDefault();
// if 'firstName' and 'email' aren't empty strings (falsy values)
if (firstName && email) {
// create an object 'person', which stores the 'firstName' and 'email' values
// to properties with the same name (ES6 syntax)
const person = { id: new Date().getTime().toString(), firstName, email };
// update the 'people' list by appending the newly created 'person' object.
setPeople((people) => {
return [...people, person]; // uses ES6 spread (...) operator
});
// set 'firstName' and 'email' back to empty strings.
setFirstName("");
setEmail("");
} else {
// if either 'firstName' or 'email' is an empty string, log message
console.log("empty values");
}
};
return (
<article>
<form className="form">
<div className="form-control">
<label htmlFor="firstName">Name: </label>
<input
type="text"
id="firstName"
name="firstName"
value={firstName}
onChange={(e) => setFirstName(e.target.value)}
autoComplete="off"
/>
</div>
<div className="form-control">
<label htmlFor="email">Email: </label>
<input
type="text"
id="email"
name="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
autoComplete="off"
/>
</div>
<button type="submit">add person</button>
</form>
</article>
);
};
However, when there are multiple input variables, it is inconvenient to have a corresponding state variable for each and every input field, because it is harder to maintain the code.
For example, when an input needs to be added or removed, another state variable must be set or deleted.
On the other hand, if you set a single state variable that holds all input values, you only need to add or remove necessary input fields without having to change the state variables. You just need to connect the value
and onChange
attribute of the <input>
element to the state variable.
Using Objects for State Variables
We can easily do this by setting an object that holds all the input values to a state variable:
const [person, setPerson] = useState({ firstName: "", email: "", age: ""});
Then, we can connect each <input>
's value
attribute to the person
state variable, as seen below:
<div className="form-control">
<label htmlFor="firstName">Name : </label>
<input
type="text"
id="firstName"
name="firstName"
value={person.firstName} // value is set to the corresponding key of the 'person' state value.
onChange={handleChange}
/>
</div>
<div className="form-control">
<label htmlFor="email">Email : </label>
<input
type="text"
id="email"
name="email"
value={person.email}
onChange={handleChange}
/>
</div>
<div className="form-control">
<label htmlFor="age">Age : </label>
<input
type="text"
id="age"
name="age"
value={person.age}
onChange={handleChange}
/>
</div>
Using ES6 Destructuring and Dynamic Object Keys to Update State
Now we can define the handleChange
method, which we connect to the onChange
attribute of the <input>
, the redefine the handleSubmit
method.
handleChange()
/*
*/
const handleChange = (e) => {
const name = e.target.name; // takes the 'name' attribute's value of the input field being triggered.
const value = e.target.value; // takes the 'value' of the 'event.target', which is the input being generated by the user.
setPerson({ ...person, [name]: value }); // destructures the 'person' object, then allows property with key corresponding to 'name' have a value corresponding to 'value' assigned.
};
handleSubmit()
const handleSubmit = (e) => {
e.preventDefault();
if (person.firstName && person.email && person.age) {
const newPerson = { ...person, id: new Date().getTime().toString() };
setPeople([...people, newPerson]);
setPerson({ firstName: "", email: "", age: "" });
}
};
Key Takeaways
- Create one state variable that manages all input data by using an object literal.
- Use dot notation to access desired properties when updating specific input values.
- Use ES6 destructuring and dynamic object property keys to update the state variable.