Have you ever found yourself in a situation where you needed to use too many useState
hooks in a single component? Here's an example of what I mean:
import { useState } from "react";
export const App = () => {
const [name, setName] = useState("");
const [something, setSomething] = useState("");
const [email, setEmail] = useState("");
const [phone, setPhone] = useState("");
const [courseStartDate, setCourseStartDate] = useState(null);
const [courseEndDate, setCourseEndDate] = useState(null);
return (
<div>
<input value={name} onChange={(e) => setName(e.target.value)} />
<p>{name}</p>
</div>
);
};
Let's assume this component is used to update user data, such as someone who has signed up for a course. Now, let's first look at the problems with the above approach and then explore solutions and alternatives in this article.
What's the Problem?
First, there's no validation or safeguards. For example, in the current approach, there's no way to check if courseEndDate
should be the day after courseStartDate
. There's also no validation for the name input, which shouldn't contain numbers, etc.
Of course, we can create separate handlers for all these functionalities, but that would make the code too long and cumbersome. It's always better to centralize such checks in one place.
import { useState } from "react";
export const App = () => {
const [name, setName] = useState("");
const [something, setSomething] = useState("");
const [email, setEmail] = useState("");
const [phone, setPhone] = useState("");
const [courseStartDate, setCourseStartDate] = useState(null);
const [courseEndDate, setCourseEndDate] = useState(null);
const handleNameChange = (e) => {
const hasNumber = /\d$/.test(e.target.value);
if (!hasNumber) setName(e.target.value);
};
const handleEndDate = (value) => {
if (courseEndDate > courseStartDate) setCourseEndDate(value);
};
return (
<div>
<input value={name} onChange={handleNameChange} />
<p>{name}</p>
</div>
);
};
What's the Alternative?
By using useReducer
, we could transform the above code like this:
import { useReducer } from "react";
export const App = () => {
const [data, updateData] = useReducer(
(prev, next) => {
const updatedData = { ...prev, ...next };
const hasNumber = /\d$/.test(updatedData.name);
if (!hasNumber) updatedData.name = prev.name;
if (updatedData.courseEndDate > updatedData.courseStartDate)
updatedData.courseEndDate = updateData.courseStartDate;
return updatedData;
},
{
name: "",
email: "",
something: "",
phone: "",
courseStartDate: null,
courseEndDate: null,
}
);
return (
<div>
<input
value={data.name}
onChange={(e) => updateData({ name: e.target.value })}
/>
<p>{data.name}</p>
</div>
);
};
As you can see above, all the validations are now in one function, and I bet that this approach makes the code much cleaner.
Why Not useState
?
You might wonder if you can achieve the same with just useState
without using useReducer
. However, here's the catch: with useState
, you would have a single state containing all the properties, but you wouldn't be able to centralize all those validations in one place as we did with useReducer
.
One Important Warning
As you noticed in the above code, we used the spread operator (...
) to ensure we don't mutate the object. We need to remember the importance of immutability here because if we mutate the state directly, React might not re-render as expected.
With this approach, you have a centralized place in the code to add validations as needed. Just call updateData()
and that's it. As you can see, this makes everything more configurable in a simple way.
Conclusion
This doesn't mean you should never use useState
. Both hooks are equally important, and it's crucial to decide when to use each. Always keep in mind that the state in useReducer
must be treated as immutable. You can also combine this approach with useContext
to share the state across multiple components, making it even more powerful.
I hope you found this useful, and I encourage you to keep exploring more tips and tricks in React! 😉
Source: https://javascript.plainenglish.io/stop-using-too-many-usestate-in-react-e613e021b33c