React.useReducer Reducer Patterns, Part 2
In part 1, we looked at reducer patterns that didn’t use any parameters or only used the current state to calculate the next state. We also learned that the primary job of a reducer is to produce a new state.
Let’s continue exploring other reducer patterns.
Table of Contents
- Reducer Using 
action - Implement a Simple 
useState - Implement a State Updater
 - Dispatch Functions as Actions
 - Takeaways
 - Intermission
 
Reducer Using action
Remember that (currentState, action) => newState is the complete reducer
signature. In the patterns we’ve explored so far, we haven’t used the action
parameter. We know the hook automatically supplies the current state to the
reducer, but how does a reducer receive its action parameter? It receives the
action parameter via the dispatch function returned by the hook: [state, dispatch] = useReducer(reducer). The dispatch function takes a single
optional parameter: dispatch(action).
Use Both currentState and action Parameters
Let’s look at an example where we use both parameters with the reducer
function.
const reducer = (count, valueToAdd) => count + valueToAdd;const [count, addToCount] = React.useReducer(reducer, 0);
return (  <main>    <div>Count: {count}</div>    <form      onSubmit={(e) => {        e.preventDefault();        const valueToAdd = Number(e.currentTarget.numberToAdd.value);        addToCount(valueToAdd);      }}    >      <label>        Add to count:{" "}        <input          name="numberToAdd"          type="number"          defaultValue={1}          style={{ width: "4em" }}        />      </label>      <button>Add</button>    </form>  </main>);What’s happening? When we submit the form, we dispatch (addToCount) an action
(the value from <input name="numberToAdd">). The reducer takes the current
state (count) and the action (valueToAdd) to produce a new state (count + valueToAdd).
Many examples of actions you’ve seen use the shape { type, payload }. While
this is a common convention, an action can be anything you like (including
undefined). In this example, an action is simply a number.
Try It Out
Use action Param Only
Just like how we can choose to only use the currentState param in a reducer,
we can choose to only use the action parameter.
const reducer = (_, newCount) => newCount;const [count, setCount] = React.useReducer(reducer, 0);
return (  <main>    <div>Count: {count}</div>    <form      onSubmit={(e) => {        e.preventDefault();        const valueToAdd = Number(e.currentTarget.numberToAdd.value);        setCount(count + valueToAdd);      }}    >      ...    </form>  </main>);By switching up a few lines, our reducer now only relies on the action
(newCount) to calculate its new state.
🤔 Wait a minute, that looks a lot like React.useState.
Try It Out
Implement a Simple useState
Replace
const reducer = (_, newCount) => newCount;const [count, setCount] = React.useReducer(reducer, 0);with
const reducer = (_, newState) => newState;const useState = (initialState) => React.useReducer(reducer, initialState);const [count, setCount] = useState(0);We have ourselves a simple useState! You can see that React.useState is
syntactic sugar for React.useReducer, simplifying the common use case of
updating a single state value. A complete re-implementation of useState is a
few more lines of code. See Kent C. Dodd’s “How to implement useState with
useReducer” if you’re interested in an in-depth
explanation.
Try It Out
Implement a State Updater
Use Case
We have a user profile form that allows users to change each value in the state object.
Solution
One elegant solution is to use the object spread syntax to create the next state.
const reducer = (info, updates) => ({ ...info, ...updates });const initialValue = {  name: "Pat Doe",  twitter: "@pdough",  email: "pat@pdough.me",  website: "https://pdough.me",};const [info, update] = React.useReducer(reducer, initialValue);
return (  <main>    <pre>{JSON.stringify(info, null, 2)}</pre>    <form>      {Object.entries(info).map(([key, value]) => (        <label key={key} style={{ display: "block" }}>          {key}:{" "}          <input            value={value}            onChange={(e) => update({ [key]: e.currentTarget.value })}          />        </label>      ))}    </form>  </main>);Try It Out
Dispatch Functions as Actions
Wait, say what? 🤔
const reducer = (count, action) => action(count);const [count, dispatch] = React.useReducer(reducer, 0);const add = (value) => dispatch((count) => count + value);const subtract = (value) => dispatch((count) => count - value);
return (  <main>    <div>Count: {count}</div>    <form>      <label>        Change count by:{" "}        <input          name="modifier"          type="number"          defaultValue={1}          style={{ width: "4em" }}        />      </label>      <button        type="button"        onClick={(e) => add(Number(e.currentTarget.form.modifier.value))}      >        Add      </button>      <button        type="button"        onClick={(e) => subtract(Number(e.currentTarget.form.modifier.value))}      >        Subtract      </button>    </form>  </main>);Yup, as stated earlier, an action can be anything you like. In this example, we’re dispatching callback functions! 🤯
Try It Out
Takeaways
- Dispatched actions can take any shape you want: 
undefined, a value, an object, or even a function. - The 
actionparameter is the value shared between thedispatch()andreducer()functions, acting as their private contract. - React.useState is syntactic sugar for one specific use case of React.useReducer.
 
Intermission
You can implement a reducer in any way you like, as long as it fulfills the
contract of ([currentState], [action]) => newState. You have complete freedom
in deciding what an action looks like, and how you want to calculate the new
state.
We’ve explored different ways of writing the reducer function, and we haven’t
even come across the familiar switch statement yet. Don’t worry, we’ll get to
that in part 3.
George Song