React Hooks: useReducer

Photo by Anne Nygård on Unsplash

Hooks have certainly changed the game in React. Previously, the use of state in React felt very rigid, and passing data around was messy at best. Using class components, our state was stored as one single collection of data. And since the direct mutation of data is frowned upon, if your state changed you had to copy all the data in the state into a new, updated version. Depending on the size and intricacy of your component and state object, this could get confusing. Once your state started to get large, you could always separate your component into smaller pieces, but sometimes this might just add more unnecessary complexity and bloat to your project.

Now with hooks we have more elegant solutions for these problems. We can use our useState hook to hold less complex state, that can be updated separately from any other state that component may have. This keeps code easy to read, follow, and change without worrying about corrupting other state. But sometimes you need a more complex way of storing and mutating your state data.

This is where useReducer can come in handy. If useState is a more light weight version of our previous class component style state, useReducer can be thought of as a more powerful, “complex” way of interacting with our state, while still keeping our individual pieces of state separated. With it we can even move our intricate state handling logic out of the component itself, and import the our state handling logic, (our reducer), from a separate file if we need to. This keeps both files easier to read, more reusable, and doesn’t split our component up into strange parts.

If you’ve ever used Redux before for state management, then the flow of useReducer will seem very familiar to you. The hook uses a dispatch method, that sends our data logic, called an action, to the reducer callback function that handles our logic. The reducer logic is often handled with a switch statement to determine what action to take. The full useReducer declaration looks like this:

There’s a bit to unpack here. First we’re using destructuring to assign our state and dispatch variables with the data returned to us from calling useReducer. The state variable is the name of the state object we are keeping track of. The dispatch method is what we are going to call when we want to change that state. The first argument we pass to useReducer is our reducer callback function. This is the function that’s going to be called when we use our dispatch method. Our last two arguments offer us different ways of setting the initial value of our state object. Argument number two, initialArg, can be set as a variable elsewhere or directly inline. The third argument, init, can optionally be used to extract out your initial state even further, and can also be used as a method for resetting back to your initial state. We’re just going to cover a basic use-case here, but know that you can, and in some cases may need, to add more nuance to your initialization functionality. For now, let’s start with the basic flow of using this hook.

NOTE: The above onClick event handlers only contain quotes around them to get the snippets to embed properly on Medium. If copying code, be sure to remove the quotes from around ‘onClick’.

In the example here we have a DogPound component, and we’re displaying a counter to track the number of dogs available for adoption. Before we render our DogPound component onto the page, we first declare our state. Here we’re calling our state dogs. We also declare our dispatch method so we can update our state.

We then pass our reducer, and the initial value of our state to the useReducer hook. As you can see, our initial state is an object that holds count, which we’re instantiating at 0. Therefore our state, dogs.count, is displayed with the initial value of 0. We then add buttons with event listeners to change our state, via our dispatch method. Just like in Redux, dispatch passes an object called action to our reducer function.

Our action typically contains a type attribute that we use to decide what action to take when we hit our reducer. Another important thing it sends over is the current value of our state. This is what makes the useReducer function so powerful. Since the current state is sent with the dispatch method, we can move the function to it’s own separate file and import it into our components, without having to give the reducer file explicit access to our state. And if you need even more data passed along, it can also contain a payload attribute, or any other logic you want to send over with it.

So now dispatch has been called, which in turn calls our useReducer’s callback function with our action object as an argument. In this case, hitting the + button sends an action.type of ‘increment’, and our - button sends action.type of ‘decrement’.

Our counterReducer’s first argument is state, which is the current value of our specific state object. We then use a switch statement to alter the state, based on the action.type that was passed in. If an object is returned from our reducer function, that object will be the new value of our state. However if the returned state is exactly the same as the previous state, useReducer is smart enough to not re render the component.

And there you have it. We have functional component state that can be extracted away into different components to increase readability and reusability. And while in this example we listed them all in one file for ease of reading, you can completely extract all the individual components into separate files, without having to give each component access to an all encompassing state store.

NOTE: The above onClick event handlers only contain quotes around them to get the snippets to embed properly on Medium. If copying code, be sure to remove the quotes from around ‘onClick’.

It can take a minute to wrap your head around the functionality of useReducer, so you should definitely play around with it yourself to get a good feel for how it works. But once you have an understanding of how and when to use it, it can make your code much easier to read and reuse.

Further reading:

Official React documentation

Getting To Know the useReducer React Hook



Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Jeremy Wood

Jeremy Wood

I’m a full stack engineer who loves to learn, solve problems, and fix things! When I’m not working on my code, you’ll usually find me working on my motorcycles.