Self-paced

Explore our extensive collection of courses designed to help you master various subjects and skills. Whether you're a beginner or an advanced learner, there's something here for everyone.

Bootcamp

Learn live

Join us for our free workshops, webinars, and other events to learn more about our programs and get started on your journey to becoming a developer.

Upcoming live events

Learning library

For all the self-taught geeks out there, here is our content library with most of the learning materials we have produced throughout the years.

It makes sense to start learning by reading and watching videos about fundamentals and how things work.

Search from all Lessons


Login
← Back to Lessons

What is and How to Use The useReducer Hook in React.js

What is useReducer

What is useReducer

Hooks were introduced in React starting from version 16.8. Since then, they have allowed for better state and logic management in functional components.

The useReducer hook is an alternative to useState for handling complex states. It is based on the reduction pattern, where a "reducer" function receives the current state and an action, and returns a new state. It is a lightweight solution compared to Redux or Flux and is ideal when a component requires multiple state updates based on different actions.

Example of useReducer

Here is a basic example of useReducer:

1const initialCounter = () => ({ counter: 0 }); 2const [state, dispatch] = useReducer(counterReducer, initialCounter());

The useReducer hook takes two arguments:

  • Reducer: A function that determines how to change the state based on the received action.

  • Initial state: It can be an object or a function that returns an initial state.

The dispatch is a function that allows sending actions to modify the state.

The reducer function

The reducer receives the current state and an action to generate a new state:

1function counterReducer(state, action) { 2 switch (action.type) { 3 case "INCREMENT": 4 return { ...state, counter: state.counter + 1 }; 5 case "DECREMENT": 6 return { ...state, counter: state.counter - 1 }; 7 case "PLUSTEN": 8 return { ...state, counter: state.counter + 10 }; 9 case "MULTIPLYBYTWO": 10 return { ...state, counter: state.counter * 2 }; 11 case "RESET": 12 return { ...state, counter: 0 }; 13 default: 14 return state; 15 } 16}

Each action is an object with a type property that defines the operation to perform. If the action does not match any case, the reducer returns the state unchanged.

Why use useReducer?

When a state is simple, useState is usually sufficient. However, in cases where there are multiple actions affecting the state, useReducer helps keep the logic organized and reusable.

We are used to perceiving components as the unit that groups the view and the logic for its operation. For example, in the following code, there is a Counter component that has the HTML to define how a numeric counter should look, and also the logic of how it should increment by one each time the "+1" button is pressed.

1export default function Counter() { 2 // Logic ⬇️ 3 const [counter, setCounter] = useState(0); 4 const increment = () => setCounter(counter + 1); 5 6 // View ⬇️ 7 return ( 8 <div className="container"> 9 <h2>State counter</h2> 10 <h3>{counter}</h3> 11 <div className="buttons"> 12 <button onClick={increment}>+1</button> 13 </div> 14 </div> 15 ); 16}

But what if we need to reuse the logic in other components? We could talk about centralized states, but what if I just want to reuse the logic, and each component has its own state? A less practical solution would be to copy and paste or export functions from a separate file and make them work with each component's state 😰. That doesn't sound convenient...

The solution to this problem is useReducer, which, as its name suggests, the function is to reduce a state and its logic to a reusable unit, allowing it to be exported from a file to the components that need it 💪. This reducer will coexist with the rest of the typical syntax of a React component; you can learn more here.

Migrating from useState to useReducer

With useState, a counter with several actions would look like the following example where we have a counter that not only increments by 1 but also has other options to modify its value.

react counter using state

1export default function CounterUsingState() { 2 const [counter, setCounter] = useState(0); 3 4 return ( 5 <div className="container"> 6 <h2>State counter</h2> 7 <h3>{counter}</h3> 8 <div className="buttons"> 9 <button onClick={() => setCounter(counter + 1)}>+1</button> 10 <button onClick={() => setCounter(counter - 1)}>-1</button> 11 <button onClick={() => setCounter(0)}>0</button> 12 <button onClick={() => setCounter(counter + 10)}>+10</button> 13 <button onClick={() => setCounter(counter * 2)}>x2</button> 14 </div> 15 </div> 16 ); 17}

With useReducer, we can move the logic to a separate file:

1// counterReducer.js 2export const initialCounter = () => ({ counter: 0 }); 3 4export default function counterReducer(state, action) { 5 switch (action.type) { 6 case "INCREMENT": 7 return { ...state, counter: state.counter + 1 }; 8 case "DECREMENT": 9 return { ...state, counter: state.counter - 1 }; 10 case "PLUSTEN": 11 return { ...state, counter: state.counter + 10 }; 12 case "MULTIPLYBYTWO": 13 return { ...state, counter: state.counter * 2 }; 14 case "RESET": 15 return { ...state, counter: 0 }; 16 default: 17 return state; 18 } 19}

And now in the component, we use useReducer:

1import React, { useReducer } from "react"; 2import counterReducer, { initialCounter } from "./counterReducer"; 3 4export default function CounterUsingReducer() { 5 const [state, dispatch] = useReducer(counterReducer, initialCounter()); 6 7 return ( 8 <div> 9 <h2>Reducer counter</h2> 10 <h3>{state.counter}</h3> 11 <div> 12 <button onClick={() => dispatch({ type: "INCREMENT" })}>+1</button> 13 <button onClick={() => dispatch({ type: "DECREMENT" })}>-1</button> 14 <button onClick={() => dispatch({ type: "RESET" })}>0</button> 15 <button onClick={() => dispatch({ type: "PLUSTEN" })}>+10</button> 16 <button onClick={() => dispatch({ type: "MULTIPLYBYTWO" })}>x2</button> 17 </div> 18 </div> 19 ); 20}

Now the state logic is completely reusable in other components.

Let's see both cases in action

All set

We have seen the advantages of useReducer and know how to extract the logic from our state to a reducer located in an external file that other components can reuse. This does not mean you have to completely discard useState and only use useReducer; like everything in programming, it's about using the right tool for the right job. You can learn more about React and its tools in this category.

Reducers are ideal when many functions are associated with the state, and grouping logic and data is convenient. This can occur in a scenario of great complexity or when functions and states need to be reused in multiple components, then you will have the powerful tool of useReducer in your arsenal.