React Reducer Hook React Typescript

Apr 15th, 2022 - written by Kimserey with .

useReducer hook in React allows us to manage state in a redux manner. This is useful for handling complex state in a single object, as opposed to useState which is better used with single variable state. With useReducer, we can define a reducer, a set of actions to interact with the state and use a dispatcher to dispatch the actions from our component. In today’s post we will look at how we can use useReducer with example.

useReducer Hook

useReduce provides a way to manage state in a Redux manner. The main components are the reducer and the actions. We start by defininig the state in an interface:

1
2
3
interface CounterState {
  count: number;
}

Then we can define the actions that we will use to modify the state:

1
2
3
4
5
6
7
8
9
10
11
12
13
interface IncrementCounter {
  kind: "Increment";
}

interface DecrementCounter {
  kind: "Decrement";
}

interface ResetCount {
  kind: "Reset";
}

type CounterAction = IncrementCounter | DecrementCounter | ResetCount;

The actions should ideally all share a property kind or type which will be used in the reducer to identify which action has been dispatch.

Next we define the reducer:

1
2
3
4
5
6
7
8
9
10
const reducer = (state: CounterState, action: CounterAction): CounterState => {
  switch (action.kind) {
    case "Increment":
      return { count: state.count + 1 };
    case "Decrement":
      return { count: state.count - 1 };
    case "Reset":
      return { count: 0 };
  }
};

We do a switch on the property that determines the action to execute and we apply to the state. A reducer is a function which takes the previous state and the action, and return the next state.

We now have all the components to use useReducer:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const App: FC = () => {
  const initialState = {
    count: 0,
  };

  const [state, dispatch] = useReducer(reducer, initialState);

  return (
    <>
      <button onClick={() => dispatch({ kind: "Increment" })}>Increment</button>
      <button onClick={() => dispatch({ kind: "Decrement" })}>Decrement</button>
      <button onClick={() => dispatch({ kind: "Reset" })}>Reset</button>
      <p>{state.count}</p>
    </>
  );
};

We can see that useReducer returned a tuple composed by the state and a dispatch function. The dispatch function is used to dispatch an action from our component.

Invoke Actions From Children

The advantage of using a reducer with actions is that we can pass down dispatch to provide all functionalities to child components.

1
2
3
4
5
6
7
8
9
10
11
const ButtonComponent: FC<{ dispatch: Dispatch<CounterAction> }> = ({
  dispatch,
}) => {
  return (
    <div>
      <button onClick={() => dispatch({ kind: "Increment" })}>Increment</button>
      <button onClick={() => dispatch({ kind: "Decrement" })}>Decrement</button>
      <button onClick={() => dispatch({ kind: "Reset" })}>Reset</button>
    </div>
  );
};

We can see that we provided all the functionalities from a single props dispatch.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const App: FC = () => {
  const initialState = {
    count: 0,
  };

  const [state, dispatch] = useReducer(reducer, initialState);

  return (
    <>
      <ButtonComponent dispatch={dispatch}></ButtonComponent>
      <p>{state.count}</p>
    </>
  );
};

We can go even a step further if we want to globally provide the dispatcher to all children, we can provide it in a context:

1
2
3
const DispatcherContext = React.createContext<
  { dispatch: Dispatch<CounterAction> } | undefined
>(undefined);

After defining the context we can use it in our children components:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const ButtonComponent: FC = () => {
  const dispatcher = useContext(DispatcherContext);

  return (
    <div>
      <button onClick={() => dispatcher?.dispatch({ kind: "Increment" })}>
        Increment
      </button>
      <button onClick={() => dispatcher?.dispatch({ kind: "Decrement" })}>
        Decrement
      </button>
      <button onClick={() => dispatcher?.dispatch({ kind: "Reset" })}>
        Reset
      </button>
    </div>
  );
};

We can see that we no longer need to pass the dispatch function down and instead we can get it from the context using the useContext hook.

And lastly we provider it from the parent component:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const App: FC = () => {
  const initialState = {
    count: 0,
  };

  const [state, dispatch] = useReducer(reducer, initialState);

  return (
    <DispatcherContext.Provider value=>
      <ButtonComponent></ButtonComponent>
      <p>{state.count}</p>
    </DispatcherContext.Provider>
  );
};

And that concludes today’s post!

Conclusion

Today we looked at how to use useReducer hook to implement state management in a Redux manner in React. We started by looking at how to use the hook, we then moved on to look at the different options we had to provide functionality of the parent state to child components. I hope you liked this post and I’ll see you on the next one!

External Sources

Designed, built and maintained by Kimserey Lam.