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.

Full-Stack Software Developer - 16w

Data Science and Machine Learning - 16 wks

Search from all Lessons


LoginGet Started
← Back to Lessons

Weekly Coding Challenge

Every week, we pick a real-life project to build your portfolio and get ready for a job. All projects are built with ChatGPT as co-pilot!

Start the Challenge

Podcast: Code Sets You Free

A tech-culture podcast where you learn to fight the enemies that blocks your way to become a successful professional in tech.

Listen the podcast
Edit on Github

Optimizing your components with useReducer

What is the useReducer hook?

What is the useReducer hook?

The hooks were launched on version 16.8 of React. Since then all the architecture of react has transformed into a series of hooks that allow the implementation of most of the most important coding design patterns.

useReducer is a proposal from React to separate the logic from the view of your components. There are other solutions like Redux, Flux, Global Context, etc; however, useReducer is easy to use and keeps the data in a local scope, which means that even when the components are reusing the functions, they don't share data.

Example of a useReducer

The first step is to declare a reducer function wich is defined with 2 parameters: The state that has all the data of the reducer, and an actions object that is used to identify the actions can be performed to manipulate the state.

1function counterReducer(state , action = {}) { 2 // Here the reducer receives the state and execute the actions 3 // at last, it returns a new state. 4}

This reducer function is in charge of mutate (modify) the state of your component according to the predefined action types, and it must return the new version of the state which replaces entirely the previous one at the end of the execution, which is why you must be careful to only write what you need and keep all the other values intact by using destructuring πŸ€“.

1function counterReducer(state , action = {}) { 2 // Whatever you do, always return a new state 3 //πŸ‘**YES** 4 return { ...state, counter: state.counter + 1 } 5 6 //🚫**NO** 7 //return { counter: state.counter + 1 } 8}

This function is meant to be used as the first parameter of the useReducer hoook. As a second parameter, it receives a function that returns an object with the initial values of the state.

The hook call returns an array of two values that represents the state (state) and the dispatcher: The object that call the executions of actions that perform the logic of the reducer (actions).

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

Inside the reducer, the object actions contain the property type that indicates which action has been invoked, and we can write the logic to mutate the state entirely.

1export default function 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 "MULTYPLYBYTWO": 10 return { ...state, counter: state.counter * 2 }; 11 case "RESET": 12 return { ...state, counter: 0 }; 13 default: 14 // In the case of having no type it returns the state intact 15 return state; 16 } 17}

With this, we can have the functions counterReducer and intitialCounter exported from a file, to be utilized by another component πŸ‘Œ.

Why use useReducer?

We are used to perceive the 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 counter of numbers should look like and there is also the logic of how it adds a unit each time the "+1" button is pressed.

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

What if we need to reuse only the logic in other components? We could consider centralized states, but what if I want to reuse only the logic while leaving every component with its own state? The janky solution would be copying the functions to another file, exporting them from there, and figuring out a way to make them work with every single state component 😰. It doesn't sound convenient...

One solution for this issue is useReducer, which as its name suggests reduces the state and the logic to a single reusable unit, allowing it to be exported from a file to every component that needs it πŸ’ͺ. This reducer will coexist with the rest of the ordinary component syntax, you can learn more here.

Migrating from useState to useReducer

In this example, we have a counter that not only adds one by one but also has other options to modify its value.

react counter using state

To perform all these actions it needs functions for every single one of them, besides the state itself. For that we'll use the classic useState hook, learn more here.

1export default function CounterUsingState() { 2 const [counter, setCounter] = useState(0); 3 const increment = () => setCounter(counter + 1); 4 const decrement = () => setCounter(counter - 1); 5 const reset = () => setCounter(0); 6 const plusten = () => setCounter(counter + 10); 7 const multiplyByTwo = () => setCounter(counter * 2); 8 9 return ( 10 <div> 11 <h2>State counter</h2> 12 <h3>{counter}</h3> 13 <div> 14 <button onClick={increment}>+1</button> 15 <button onClick={decrement}>-1</button> 16 <button onClick={reset}>0</button> 17 <button onClick={plusten}>+10</button> 18 <button onClick={multiplyByTwo}>x2</button> 19 </div> 20 </div> 21 ); 22}

This works perfectly, but to make this logic reusable and move it to another file, let's convert it into a reducer:

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

Now from the component we can import and use the reducer:

1import React, { useReducer } from "react"; 2import counterReducer, { intitialCounter } from "./counterReducer"; 3 4export default function CounterUsingReducer() { 5 // Add the hook useReducer, passing as arguments 6 // our reducer function and the initializer, 7 // being both imported from another file. 8 const [state, dispatch] = useReducer(counterReducer, intitialCounter()); 9 10 return ( 11 <div> 12 <h2>Reducer counter</h2> 13 {/* Now the counter is inside the reducer's state */} 14 <h3>{state.counter}</h3> 15 <div> 16 17 {/* We call the dispatch function passing the type of the action to perform the reducer's logic */} 18 <button onClick={() => dispatch({ type: "INCREMENT" })}>+1</button> 19 <button onClick={() => dispatch({ type: "DECREMENT" })}>-1</button> 20 <button onClick={() => dispatch({ type: "RESET" })}>0</button> 21 <button onClick={() => dispatch({ type: "PLUSTEN" })}>+10</button> 22 <button onClick={() => dispatch({ type: "MULTYPLYBYTWO" })}>x2</button> 23 </div> 24 </div> 25 ); 26}

For this to work it was necessary to use the state of the reducer and replace the functions for the calls to dispatch, which runs the logic of the reducer and receives as a parameter the type of action to executer.

Let's see both cases in action

And that's it

We have seen the advantages of useReducer and now we know how to extract the logic and the state to a reducer exported on an external file that can be reused by other components. This doesn't mean you have to dish out useState entirely and only use useReducer. Like everything in coding is about using the right tool for the right job. You can learn more about React and the great tools it has in this category

The reducers are ideal when we have a lot of functions associated with a single state, and turns out convenient to group logic and data. This can happen in a scenario of great complexity o when you need to reuse functions and their state across many components, then you will have the mighty tool useReducer in your arsenal.