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 useReducer hook receives as the first parameter a function that defines the reducer and will return an array of two values that represents the state of the reducer (state) and the object that allows dispatching the actions that perform the logic of the reducer (actions). As a second parameter, it receives a function that returns an object with the initial values of the state.

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

At the same time, the reducer function itself is defined with 2 parameters: The state that has all the data of the reducer, and an object that is used to identify the actions that must be performed inside it (which we'll call actions).

1function counterReducer(state , action = {}) { 2 // Here the reducer receives the state and execute the actions 3}

This reducer function will be executed on every action call 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 πŸ€“.

πŸ‘YES

1return { ...state, counter: state.counter + 1 }

🚫NO

1return { counter: state.counter + 1 }

Inside the reducer, the object actions contain the property type that indicates to us which action has been invoked, and we can write the logic based on it.

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?

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.