React.useReducer Reducer Patterns, Part 1
July 19, 2020 (updated April 1, 2021)
I see a lot of people, especially those new to React hooks, shy away from
useReducer
in favor of multiple useState
s. I think some of it
is the simplicity of the useState
API, and some of it is assumed complexity
via association with Redux. In practice, useReducer
has a lot 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
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
instantiate 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 in 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 with the signal or not. 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], [dispachedAction])
. 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 actions in combination with the current state.