4Geeks logo
4Geeks logo

Courses

Explora nuestra extensa colección de cursos diseñados para ayudarte a dominar varios temas y habilidades. Ya seas un principiante o un aprendiz avanzado, aquí hay algo para todos.

Coding Bootcamp

Aprende en vivo

Únete a nosotros en nuestros talleres gratuitos, webinars y otros eventos para aprender más sobre nuestros programas y comenzar tu camino para convertirte en desarrollador.

Próximos eventos en vivo

Catálogo de contenidos

Para los geeks autodidactas, este es nuestro extenso catálogo de contenido con todos los materiales y tutoriales que hemos desarrollado hasta el día de hoy.

Tiene sentido comenzar a aprender leyendo y viendo videos sobre los fundamentos y cómo funcionan las cosas.

Full-Stack Software Developer - 16w

Data Science and Machine Learning - 16 wks

Buscar en lecciones


IngresarEmpezar
← Regresar a lecciones

Weekly Coding Challenge

Todas las semanas escogemos un proyecto de la vida real para que construyas tu portafolio y te prepares para conseguir un trabajo. Todos nuestros proyectos están construidos con ChatGPT como co-pilot!

Únete al reto

Podcast: Code Sets You Free

Un podcast de cultura tecnológica donde aprenderás a luchar contra los enemigos que te bloquean en tu camino para convertirte en un profesional exitoso en tecnología.

Escuchar el podcast
Editar en Github

Qué es y como usar el hook useReducer en React.js

Que es el useReducer

Que es el useReducer

Los hooks empezaron a existir en react desde la versión 16.8. Desde entonces, toda la arquitectura de react se ha transformado en una serie de "Hooks" que permiten implementar la mayoria de los patrones de programacion mas importantes. El useReducer es una propuesta de React para separar la logica de la vista en tus componentes. Hay otras soluciones como Redux, Flux, Global Context, etc. Sin embargo, el useReducer es sencillo de usar y mantiene un alcance local sobre los datos, es decir, a pesar de reusar las funciones y codigo de los componentes, no se compartiran los datos entre sí.

Ejemplo de useReducer

Este es el ejemplo más sencillo de useReducer:

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

El hook useReducer recibe como primer parámetro una función que define el reducer, y va a retornar un arreglo de dos valores que representan al nuevo estado (state) y el dispatcher: El objeto que permite ejecutar las acciones/funciones de la lógica del reducer (actions). Como segundo parámetro se debe pasar una función que retorne un objeto con los valores iniciales del estado.

El segundo valor del arreglo que deveulve el useReducer se llama "dispacher" y no "actions" porque es necesario tener un "despachador" de acciones como intermediario para evitar conflictos en los datos.

A su vez la función reducer (en este ejemplo se llama counterReducer) se define con 2 parámetros: El state que contiene los datos del reducer, y un objeto "actions" que se usa para identificar las acciones que podemos ejecutar para manipular el state.

1function counterReducer(state , action = {}) { 2 // Aquí el reducer recibe el estado actual 3 // luego ejecuta las acciones 4 // por ultimo retorna el estado nuevo 5}

Esta función reducer se va a ejecutar en cada llamado de acción y deberá retornar una nueva versión del estado que reemplaza por completo la anterior al terminar su ejecución, por lo que hay que ser cuidadoso y sólo alterar lo que necesitamos y retornar siempre los demás valores del estado utilizando la desestructuracion (js destructuring) 🤓.

👍SI

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

🚫NO

1return { counter: state.counter + 1 }

Dentro del reducer, el objeto actions contiene una propiedad type que nos indica que acción ha sido invocada, y podremos escribir la lógica basado en ello.

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 // En caso no tener ningún tipo se retorna el estado sin alterar 15 return state; 16 } 17}

Ya con esto podemos tener tanto las funciones counterReducer e intitialCounter exportadas en un archivo, para ser utilizadas por cualquier otro componente 👌.

Porque usar useReducer

Estamos acostumbrados a percibir los componentes como la unidad que agrupa la vista y la lógica para su funcionamiento. Por ejemplo: En el siguiente código hay un componente Counter que tiene el HTML para definir como debería verse un contador de números y tambien existe la logica de como deberia sumar una unidad cada vez que se presione el botón "+1"

1export default function Counter() { 2 3 // Logica ⬇️ 4 const [counter, setCounter] = useState(0); 5 const increment = () => setCounter(counter + 1); 6 7 // Vista ⬇️ 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}

Pero ¿Qué pasa si necesitamos reutilizar sólo la lógica en otros componentes? Podríamos hablar de estados centralizados, pero ¿Qué pasa si sólo quiero reutilizar la lógica y que cada componente tenga un estado propio? Una solución poco práctica seria copiar y pegar, o exportar las funciones desde un archivo aparte y buscar alguna manera de hacerlas trabajar con el estado de cada componente 😰. Eso no suena conveniente...

La solución a este problema es useReducer, que como dice su nombre, su función es reducir un estado y su lógica a una unidad reutilizable, permitiendo que esta se pueda exportar desde un archivo a los componentes que lo necesiten 💪. Este reducer va a cohexistir con el resto de la sintaxis típica de un componente React, puedes aprender más aquí.

Migrando de useState a useReducer

En este ejemplo tenemos un contador que no solamente suma de 1 en 1, sino también tiene otras opciones para modificar su valor.

react counter using state

Para realizar todas estas acciones se necesitan funciones para cada una de ellas, ademas del estado en si. Para eso usaremos el clasico hook useState, aprende mas aquí.

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 className="container"> 11 <h2>State counter</h2> 12 <h3>{counter}</h3> 13 <div className="buttons"> 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}

Esto funciona perfecto, pero para hacer la lógica reutilizable y moverlo a otro archivo, lo convertiremos en un 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

Ahora desde el componente importamos y hacemos uso del reducer:

1import React, { useReducer } from "react"; 2import counterReducer, { intitialCounter } from "./counterReducer"; 3 4export default function CounterUsingReducer() { 5 // Agregamos el hook useReducer, pasándole como parámetros 6 // nuestra función reducer y el inicializador, 7 // ambos importados desde otro archivo 8 const [state, dispatch] = useReducer(counterReducer, intitialCounter()); 9 10 return ( 11 <div> 12 <h2>Reducer counter</h2> 13 {/* Ahora el counter esta dentro del estado del reducer */} 14 <h3>{state.counter}</h3> 15 <div> 16 17 {/* Llamamos a la función dispatch con el tipo de acción para ejecutar la lógica del reducer */} 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}

Para que esto funcione fue necesario usar el state del reducer y reemplazar las funciones que estaban antes, por llamados a la función dispatch, que ejecuta la lógica del reducer y recibe como parámetro el tipo de la acción que se va a ejecutar.

Veamos ambos casos en acción

Todo listo

Ya hemos visto las ventajas de useReducer y sabemos como extraer la lógica de nuestro estado a un reducer ubicado en un archivo externo que pueden reutilizar los demás componentes. Esto no significa que tengas que desechar useState por completo y solo usar useReducer, como todo en programación se trata de usar la herramienta adecuada para el trabajo adecuado. Puedes aprender más de React y las herramientas que tiene en esta categoria

Los reducer son ideales cuando tenemos muchas funciones asociadas al estado, y nos convenga agrupar lógica y datos. Esto puede darse en un escenario de gran complejidad o cuando se necesite reutilizar funciones y estados en varios componentes, ahi tendrás la poderosa herramienta de useReducer en tu arsenal.