React useReducer hook
In my last post “React 16.7”, we had a look at the new React proposal version (which, by the way, it’s not a proposal for v16.7 anymore, but for v16.8) and a brief introduction to the hooks useState
and useEffect
. In this post we’ll address the useReducer
hook.
Managing component state with the useState hook
useState
is the basic hook that allows us to keep a state in a functional component. A basic usage example:
The updater function returned by the useState
hook works much the same as the previous React setState
function, except that the new one replaces the whole state with the argument passed instead of merging it with the old state.
Since we can use as many useState
functions as we want, we can create and handle component state no matter its shape.
However, if our component has to keep several data which are all part of the same logic state, useState
may become a bit cumbersome. Here is an example of a counter which can increment, decrement and reset its value and undo the last operation. To implement the undo operation, we need to keep the previous state in addition to the current state for every operation:
useReducer to the rescue!
useReducer
is another hook to manage the state in a component. It looks like this:
const [state, dispatch] = useReducer(reducer, initialState);
The reducer function should be a function that receives a state and an action and (reduces it to) returns a new state: (state, action) => newState
.
Below is the previous example but implemented using useReducer
instead of useState
:
This way, besides doing all state changes at once when an action is triggered, we can find all logic related with the same state placed together.
Dan Abramov wrapped up on a tweet the matter of when to use useState
or useReducer
to manage the component state:
Avoid passing callbacks down
With the useReducer
hook, we can simplify the problem of passing callbacks down in large tree components. Here’s an example, quite contrived but we still can see the point we are talking about:
We could simplify the example above by defining an object which collects all callbacks so we can pass it down. To avoid the hell of passing down through every component in the hierarchy, we could pass the “api object” via context. The problem is that the object would change in every rerender, so all components that read it from the context would be rerendered as well.
Abramov recommended a similar pattern but, instead of passing down an “api object”, passing the dispatch
function:
The key of this pattern is that the dispatch
function doesn’t change between rerenders. So the final solution would look like this:
Wrapping up
You can use useState
for managing any component state but there are two scenarios where useReducer
is more convenient:
- When the state keeps data which change together (like “data” and “isLoadingData”).
- When you have to pass callbacks down in large component trees.