React.useReducer Reducer Patterns, Part 1
I see many people, especially those new to React hooks, shy away from
useReducer
in favor of multiple useState
calls. I suspect
this stems partly from the simplicity of the useState
API and partly from an
assumed complexity due to its association with Redux. In practice,
useReducer
has much more in common with useState
than Redux.
Table of Contents
- Updating State With
useState
anduseReducer
- The Reducer Function
- Putting It All Together With useReducer
- Reducer Without Any Params
- Reducer Using Current State
- Takeaways
- Intermission
Updating State With useState
and useReducer
The useState
API has two parts: the state and the state setter. When you want
to update the state, you replace it using the state setter.
The useReducer
API has three parts: the state, an action dispatcher, and a
reducer to produce the new state.
When you want to update the state:
- Dispatch an action.
- The hook passes the current state and the action to the reducer function.
- The reducer computes a new state.
- The state is updated with the new state.
useReducer
takes care of steps 2 and 4. You’re responsible for steps 1 and 3.
The Reducer Function
These are all valid signatures for reducer functions:
(currentState, dispachedAction) => newState(currentState) => newState() => newState
As you can see, the primary job of a reducer is to produce a new state, optionally taking into account the current state and the dispatched action.
Putting It All Together With useReducer
The signature of useReducer
is:
const [state, dispatch] = useReducer(reducer, initialState);
You pass in a reducer
function (and optionally an initialState
) to set up a
useReducer
hook. What you get back is a read-only state
and a
dispatch
function which you’ll use to trigger the reducer
.
This is one of the key differences when compared to useState
—a useReducer
hook computes the next state internally using a reducer
function, while a
useState
hook relies entirely on external calculations for its next state.
For the rest of the article, we’ll focus on different reducer
(a.k.a. “new
state” calculator) implementation patterns.
Reducer Without Any Params
() => newState
The primary job of a reducer is to return a new state. In its simplest form, a reducer doesn’t even need to accept any parameters.
const reducer = () => new Date();const [date, update] = React.useReducer(reducer, new Date());
return ( <main> <div> Now: {date.toLocaleDateString()} {date.toLocaleTimeString()} </div> <button onClick={update}>Update</button> </main>);
The names state
, dispatch
, and reducer
are conceptual. In practice, you
can name them anything you like. In this example, they are date
(state),
update
(dispatch), and reducer
.
Notice that we invoke update()
without passing any parameters. The dispatch
function signals the hook to run the reducer function; it’s up to you whether
you need to send an action along with the signal. In this case, the hook
actually runs reducer(currentState, undefined)
.
🤔 But Wait, the Reducer Doesn’t Accept Any Params
That’s right. In JavaScript, just because you pass parameters to a function, doesn’t mean it needs to accept them.
const sayHi = () => "hi";const result = sayHi(1, 2, 3);// result === "hi"
Try the No-Params Reducer Example
Reducer Using Current State
(currentState) => newState
This form of reducer is useful when you need the current state to calculate the next state.
const reducer = (count) => count + 1;const [count, addOne] = React.useReducer(reducer, 0);
return ( <main> <div>{count}</div> <button onClick={addOne}>Add 1</button> </main>);
Once again, the dispatch function, addOne()
, doesn’t need to pass an action to
reducer
—the reducer
is capable of computing the next state entirely based on
the current state.
Try It Out
Takeaways
- The primary job of a reducer is to produce a new state.
- A reducer function can take zero, one, or two params:
([currentState], [dispatchedAction])
. dispatch()
signals theuseReducer
hook to invoke the reducer.
Intermission
😅 Whew, let’s take a break for now. In part 2, we’ll explore reducers that use actions, as well as reducers that use both actions and the current state.