React Hooks are a simpler way to encapsulate stateful behavior and side effects in functional components instead of classes. Some hooks are easier to understand than others, therefore this series of posts will focus on demystifying the hooks that are not as straightforward.
So far, we have explored useCallback, useMemo, useRef, and useContext so make sure to checkout my previous posts if you haven’t already. This week, let’s start with the basics by explaining the JavaScript reduce method. Once we explore the basics, it will be much easier to understand the useReducer hook, as well as how and when to use it in your code.
What is a reducer?
A reducer is the action that will be executed in order to get only one value. The goal of a reducer is to reduce (...duh!). The value returned could be a number, a string, an array or even an object, as long as it is a singular value. Moreover, it is important to highlight that reducers return a new value instead of mutating your initial value.
reduces are very useful when you want to obtain a single value after applying some logic to a group of values. For instance, if you want to add up an array of numbers to obtain a total value as we do in the following example.
We apply the JavaScript reduce method to an array of numbers called nums = [1,2,3,4,5]
. The reduce
method takes two parameters:
reducer - a function that provides instructions to obtain one value. In this case, to sum up all the given values in the nums
array.
const reducer = (accumulator, currentValue) => accumulator + currentValue;
initialValue - the starting point value when implementing the reducer
function's instructions. In our example, we define our initial value as 0 so the total value returned reflects only the sum of the values in the array nums
.
const initialValue = 0;
Now that we defined the elements involved, let’s see it all together. The reduce
method takes our initialValue
and builds on it by following the instructions given under the reducer
function, adding each value in the nums
array until it is able to return one total value.
// reducer method - the action that will be executed in order to get one value
const reducer = (accumulator, currentValue) => accumulator + currentValue;
// array of values that we want to add up
const nums = [1,2,3,4,5];
// initial value set to 0
const initialValue = 0;
// JavaScript reduce method receives two parameters: the reducer function and initial value defined above
const totalValue = nums.reduce(reducer, initialValue);
What is useReducer()?
The useReducer hook is used with state management. It receives the following parameters:
reducer - a function that provides instructions on how to manage state. It takes two parameters state
and action
and it returns a new state.
// reducer type
(state, action) => newState
initialState - the starting point value. It will change according to the reducer
instructions.
Does it look familiar? Well yeah... It takes similar parameters as the reduce function explained above. However, the useReducer hook does not return just one value as reduce does. Instead it returns two elements as an array, the current state
and a dispatch
function.
const [state, dispatch] = useReducer(reducer, initialState);
If you are familiar with React hooks, you probably have used useState before. Let’s compare these two hooks
// useState implementation
const [state, setState] = useState(initialState);
// useReducer implementation
const [state, dispatch] = useReducer(reducer, initialState);
useReducer and useState both return a stateful value (state
), and a function to update the state (setState
and dispatch
). In addition, both hooks receive an initial state value (initialValue
). The main difference in these two initializations is that useReducer also takes a reducer
function, which will be called when we use the returned dispatch
function. Let’s explore how useReducer works in the next section.
How to use useReducer?
Sometimes the best way to explain how something works is with an example so let’s take a look at one. Here is a definition for an initialState
, also called a store of data, that contains a list of dogs up for adoption with their name, breed, and adoption status.
const initialState = [
{
name: "Waffles",
breed: "Chihuahua",
adopted: false,
},
{
name: "Charlie",
breed: "Pitbull",
adopted: true,
},
{
name: "Prince",
breed: "German Shepherd",
adopted: false,
},
];
Now let’s create a reducer
function to update our initialState
list of dogs as they get adopted or returned. This function takes the following parameters:
state
- the current state of our dogs in adoption.
action
- an object that contains the following:
type
of action we want to perform. In this case, we are only building two options,adopt
or ‘return’.payload
optional data. In our example, we will pass the dog’sname
so we can identify which dog got adopted or returned.
const reducer = (state, action) => {
switch (action.type) {
case 'ADOPT':
return state.map(dog => {
if (dog.name === action.payload) dog.adopted = true;
return dog;
});
case 'RETURN':
return state.map(dog => {
if (dog.name === action.payload) dog.adopted = false;
return dog;
});
default:
return state;
}
}
Now It’s finally time to implement our useReducer() hook! Take a look at the example below, where we define our useReducer() hook with the initialState
(adoption dogs list) and the reducer
function we created to handle their adoption status.
We iterate through our dogs list state and display a button that will say adopt
or return
depending on their current adoption status. The onClick
handler assigned to our button will call a function in charge of using the dispatch
function returned by our useReducer() hook passing the type
of action it needs to perform and the dog’s name as payload
.
const adoptDog = name => dispatch({ type: 'ADOPT', payload: name });
The dispatch
function will pass this data to our reducer
function, where we will use the type
to identify what section of code needs to run and the payload
to find the dog record we need to update.
When should you use useReducer?
When explaining the useReducer hook, we compared it to useState so you might be wondering… when should I use useReducer and when should I use useState?
The useReducer hook is a preferred alternative to useState when dealing with the following:
- Complex state logic that involves multiple sub-values
- State values that depend on the state of other state elements
Summary
The JavaScript reduce method is very useful when you want to obtain a single value after applying some logic to a group of values.
reducers return a new value instead of mutating your initial value.
The useReducer hook is used with state management.
The useReducer hook should be used when dealing with complex state logic, multiple sub-values, or when your state depends on state sub-values.
I hope this post helped you get a better understanding of the JavaScript reduce method and the useReducer() hook and that you will start taking advantage of these concepts in your future projects.
I enjoy creating content that explains concepts in really simple terms. Why? Because knowledge is power and I want to help beginner developers expand their knowledge and thrive.
Give this post a reaction if you found it helpful and follow me on Twitter and Dev.to to keep up with new posts!
Top comments (8)
This is one of the best explanations of hooks. Excellent work.
Thank you David! :)
Your illustrations are life!
I like how you’ve wrapped your dispatches in a functions!
Will be using that for modularity :)
Thank you Adrian! I really appreciate the kind words :)
Greatly written, thanks!
Thanks Victor! :)
Thanks for the tutorial, that would be great if you can make another one that instructs how to combine useContext and useReducer, :D
Nice texts! It'll be good to see an article about the useReducer with a context and about their optimization :)