A Look At React Hooks: useReducer

Subscribe to my newsletter and never miss my upcoming articles

Listen to this article

Welcome to A Look at React Hooks, a beginner-friendly series on React Hooks. In this article, let's look at the useReducer Hook.

What is useReducer?

For those who are familiar with Redux, the useReducer Hook allows React to access reducer functions for state management. If you are not from a Redux background, a simple way to describe this Hook is that it is an alternative to the useState Hook. The difference is that it allows for more complex logic and state updates that contain multiple sub-values.

This Hook typically accepts 2 arguments: a reducer function and an initialState. Optionally, it accepts a 3rd argument, an init function which we will discuss later.

So the useReducer Hook will initialize as:

useReducer(reducer, initialState)

Similar to useState, it returns an array of 2 values which can be destructured as the current value of the state and a dispatch function.

In summary, the Hook can be initialized as:

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

The reducer function

Before we learn how to use the Hook, let's learn about one of its arguments: the reducer function. A 'reducer' is basically a function that accepts a state and an action, then returns a value.

For example:

function reducer(state, action) {
  return state + action;
}

This is how the Hook can update the values of its state. The action determines how the value of state will change. In the example above, the new value of the state will be the sum of the initial state and action.

The dispatch function

And now you may ask: how do we use pass an action to this Hook's reducer function?

Well, the dispatch function is one responsible for that. It dispatches the action to be used to update the value of state.

For example, here we have a simple button that dispatches the action of value 1. The reducer function that adds 1 to its current state and returns the new value.

<button onClick={() => dispatch(1)}>
        Add
</button>

Note that in Redux, the dispatch function usually dispatches an object in the format:

{type: 'Add', payload: 1}

Where 'type' is the description of the action and 'payload' is the value. But for useReducer, it is optional to stick to this convention.

An Example

Now that we have learnt the basics, let's see how we can implement everything we learned in an example. Let's say we want to make a simple counter app. We can increase or decrease the count state, or even reset it to zero.

As usual, we first import the Hook.

import React, { useReducer } from 'react';

Then we proceed as follows:

  1. Set an initialState to 0.
  2. Write the reducer function. Depending on the 'type' of the action, we increment, decrement or reset the state.
  3. Initialize the useReducer Hook
  4. Add the relevant dispatch function to buttons

In code, it will look like:

//1.
const initialState = 0;

//2.
function reducer(state, action) {
  switch (action.type) {
    case "increment":
      return state + 1;
    case "decrement":
      return state - 1;
    case "reset":
      return 0;
    default:
      throw new Error();
  }
}

function App() {
  //3.
  const [state, dispatch] = useReducer(reducer, initialState);

  return (
    <div>
      <h1> Count: {state}</h1>
       <!-- 4. -->
      <button onClick={() => dispatch({ type: "decrement" })}>-</button>
      <button onClick={() => dispatch({ type: "increment" })}>+</button>
      <button onClick={() => dispatch({ type: "reset" })}>Reset</button>
    </div>
  );
}

As you can see below, the app works perfectly! useReducer.gif

Important Things to Know

Now that we know how to use this Hook, it is important to know some details.

1. Local 'Store'

If you know Redux, you know that it has a centralized 'store' where the app can access variables from any of its components.

However, keep in mind that useReducer only stores data locally within its component.

2. Reducers are pure

Notice that the reducer function must be a pure function. This means that the function returns the same value if the same arguments are passed and there are no side effects.

Let's say we alter our counter app example reducer function to directly mutate the value of state instead of returning as a new value. The app will no longer work.

function reducer(state, action) {
  switch (action.type) {
    case "increment":
      return state++; //directly increasing state
    case "decrement":
      return state--; //directly decreasing state
    case "reset":
      return 0;
    default:
      throw new Error();
  }
}

3. Lazy Initialization

As mentioned earlier, the Hook optionally takes in a 3rd argument: init. init is a function that sets the initialState with init(initialArg).

Back to our counter app example, we can use the init function to set our initial state as follows:

function init(initialState) {
  return initialState;
}

Then we edit our reducer function to set state to its initial value when 'type' is "reset".

function reducer(state, action) {
  switch (action.type) {
    case "increment":
      return state + 1;
    case "decrement":
      return state - 1;
    case "reset":
      return init(action.payload); //used init instead of hard code as 0
    default:
      return;
  }
}

Finally, we make some changes to our App component and Hook.

function App() {
  //add init as the 3rd argument
  const [state, dispatch] = useReducer(reducer, initialState, init);

  return (
    <div>
      <h1> Count: {state}</h1>
      <button onClick={() => dispatch({ type: "decrement" })}>-</button>
      <button onClick={() => dispatch({ type: "increment" })}>+</button>
      <!--Add a payload property with initialState as the value-->
      <button onClick={() => 
        dispatch({ type: "reset", payload: initialState})}>Reset</button>
    </div>
  );
}

Conclusion

And that's the gist of this Hook! Thanks for reading this article. I hope it was helpful for React beginners. Please feel free to ask questions in the comments below. Ultimately, practising and building projects with this Hook will help you to pick it up faster.

The next Hook in this series will be: useCallback. Stay tuned and cheers!


Resources

Favourite Jome's photo

Wonderfully explained I must say!

Honestly I've been running away from this hook and also from Redux. I find Redux hard to understand that's why I assumed the useReducer hook too will be difficult.

So then, once I'm free I'll give useReducer hook a try and pratice with it. Thanks for sharing 👋.

Victoria Lo's photo

My pleasure Favourite! Always glad to see you learning and challenging yourself :)

Catalin's Tech's photo

Nice article! You could create a nice ebook with this series! It's amazing. 🔥

Victoria Lo's photo

Haha thanks Catalin Pit! I'm honoured you enjoyed the series :)

Richard Harris's photo

I had a hard time understanding this hook and this helps a lot

Victoria Lo's photo

Glad it helped you, Richard :D