React community is vast and growing rapidly. For the same business case, there are different ways to solve a problem.
And, whenever there are multiple solutions to do the same thing, the question arises - “When to use one over the other?”
The answer most of the time is - It depends on the use case.
One such most-asked question while handling states is -
When to use useReducer over useState hook?
Let’s understand these hooks in detail and discuss which one should be preferred and when.
The useState hook
The useState
is a hook that allows us to have state variables in functional components.
It takes the initial state as an argument
and returns an array of two entries
- State value
- A function to update the state
The useReducer hook
The useReducer(reducer, initialState) hook accept 3 arguments:
- Reducer function
- Initial state
- Initializer function
The hook then returns an array of 2 items:
- Current state
- Dispatch function
Use-case of a state with JavaScript primitives
Using the useState hook
Let’s check out the example of setting up a locale using useState
.
Pretty simple, right?
Using the useReduce hook
Now, let’s look into the useReducer hook
.
This code looks a bit more complex than the useState
code,
isn’t it?
This example involves a primitive value ‘locale’(string) which is not a complex object or an array.
So, this use case is more suitable for useState
.
Use-case of a state with an object
Now, let’s consider an example of a form taken from the React docs.
We have a user object with two fields: name and age.
Using the useState hook
Firstly let’s write the example using useState
.
If the logic of our code gets complicated then the setter function can be large and it will be difficult to handle it. Also, we have to take care of returning new items in every setter function using the spreading operator. This becomes very difficult to manage.
To simplify our lives,
we have a useReducer
hook to our rescue!
Using the useReducer hook
Now,
let’s write the same example using useReducer
.
Looks simple and clean.
Isn’t it?
useReducer
is very similar to useState
,
but it lets us move the state update logic from event handlers
into a single function outside of our component.
The point to note is when we have a type of state as objects or arrays,
it is convenient to use useReducer
.
Thumb rules to decide when to use useState
or useReducer
Prefer useState if we have:
- JavaScript primitives(string, boolean, number) as a state( eg. our first use case )
- Simple business logic
- Different properties that don’t change in any correlated way and can be managed by multiple useState hooks
Prefer useReducer if we have:
- JavaScript objects or arrays as a state ( eg. our second use case )
- Complicated business logic more suitable for a reducer function
- Different properties tied together that should be managed in one state object
Final thoughts
We should consider incrementally adopting useReducer
as our state and validation requirements begin to get more complex,
warranting the additional effort.
While adopting useReducer
for complex objects frequently,
if there are many pitfalls around mutation, introducing
Immer
could be worthwhile.
React docs recommend using a reducer if we often encounter bugs due to incorrect state updates in some components, and want to introduce more structure to its code.
Using useState
or useReducer
is a matter of preference.
We can convert between useState and useReducer back and forth: they are equivalent!
We don’t have to use reducers for everything. We can mix and match!
If the project has gotten to the point of state management complexity, we may want to look at some even more scalable solutions, such as Mobx, Zustand, or XState to meet our needs.
But let’s not forget,
Start Simple
and
Add complexity only as needed.