Reducers (Solution)

We can start by defining the shape of our state.

type CounterAction = {
  type: "INCREMENT" | "DECREMENT" | "SET";
  payload?: number;
};

type CounterState = {
  value: number;
};

Let's get a simple version of this reducer in place.

const reducer = (state: CounterState, action: CounterAction) => {
  switch (action.type) {
    case "INCREMENT":
      return { value: state.value + 1 };
    case "DECREMENT":
      return { value: state.value - 1 };
    case "SET":
      return { value: state.value + (action.payload || 0) };
  }
};

We can create our hooks and our action creators.

const [state, dispatch] = useReducer(reducer, { value: 0 });

const increment = () => dispatch({ type: "INCREMENT" });
const decrement = () => dispatch({ type: "DECREMENT" });
const reset = () => dispatch({ type: "SET", payload: 0 });
const set = (n: number) => dispatch({ type: "SET", payload: n });

Let's get the basic buttons working:

<p className="count">{state.value}</p>
<section className="controls">
  <button onClick={increment}>Increment</button>
  <button onClick={reset}>Reset</button>
  <button onClick={decrement}>Decrement</button>
</section>

An Improvement

We can make our actions even better. By getting a little more nuanced with our types.

type BasicCounterAction = {
  type: "INCREMENT" | "DECREMENT";
};

type SetCounterAction = {
  type: "SET";
  payload: number;
};

type CounterAction = BasicCounterAction | SetCounterAction;

Now, TypeScript will protect us from firing an increment or decrement action with a payload and also omitting one from the actions for setting the value.

Even better, we've given TypeScript more information, which means we can remove that check to see if action.payload is undefined in the reducer.

const reducer = (state: CounterState, action: BetterAction) => {
  switch (action.type) {
    case "INCREMENT":
      return { value: state.value + 1 };
    case "DECREMENT":
      return { value: state.value - 1 };
    case "SET":
      // No more `action.payload || 0`
      return { value: action.payload };
  }
};

The completed example is here.

Where Are We Now?

  • projects/counter-for-hooks on the incident-counter-reducer-solution branch.
  • examples/21-incident-counter-reducer-solution
  • CodeSandbox