Using the React useState hook for forms
The React useState
hook can be used many times to manage as many state variables as necessary in a given component. This is especially useful for forms, which often have several fields that need to be tracked in a component's local state.
Manage form state independently
Let's take a look at an example of a basic form with two fields, an email
and a password
field. In this example, we will manage the email
and password
states separately as individual state variables.
import React, { useState } from "react";
const Login = () => {
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const handleSubmit = (e) => {
// prevent page reload submit behavior
e.preventDefault();
console.log(email, password);
};
return (
<form onSubmit={handleSubmit}>
<input
name="email"
type="email"
placeholder="Email"
onChange={(e) => setEmail(e.target.value)}
/>
<input
name="password"
type="password"
placeholder="Password"
onChange={(e) => setPassword(e.target.value)}
/>
<button type="submit">Submit</button>
</form>
);
}
Each field has its own setter function and each field has its own state reference. The email field is referenced by email
and is updated by setEmail
. The password field is referenced by password
and is updated by setPassword
.
Manage form state in one object
We can also use useState
to manage form state within one single object. This approach only needs one useState
call. Rather than email
and password
being managed by separate state variables, they become properties of one state object.
The tricky part with this approach is properly updating the state, which is now an object.
When using useState
with an object, we must remember to spread in the previous state when performing updates to this object. This allows the object to be updated properly without any unexpected results. It is not done automatically for us. Therefore, we must be sure to manually merge the previous state object's properties with the new state changes.
import React, { useState } from "react";
const Login = () => {
const [state, setState] = useState({
email: '',
password: ''
});
const handleChange = (e) => {
setState({
// spread in the previous state
...state,
[e.target.name]: e.target.value,
});
};
const handleSubmit = (e) => {
// prevent page reload submit behavior
e.preventDefault();
console.log(state);
};
return (
<form onSubmit={handleSubmit}>
<input
name="email"
type="email"
placeholder="Email"
onChange={handleChange}
/>
<input
name="password"
type="password"
placeholder="Password"
onChange={handleChange}
/>
<button type="submit">Submit</button>
</form>
);
}
export default Login;
We could have also used an updater function for our setState
setter function.
const handleChange = (e) => {
setState((prevState) => ({
...prevState,
[e.target.name]: e.target.value,
}));
};
Though it's a bit more verbose, it's best to go with this updater function approach when the next state of a variable depends on its previous state.
Conclusion
We've looked at two different approaches to effectively using useState
for managing form state in React. If your form consists of many fields, consider managing the form's state in one object. If your form consists of just a few fields, managing form state as independent fields should be fine.