A tu propio ritmo

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.

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.

Buscar en lecciones


Ingresar
← Regresar a lecciones

Qué es y cómo usar el hook useReducer en React.js

Qué es useReducer

Qué es useReducer

Los Hooks se introdujeron en React a partir de la versión 16.8. Desde entonces, han permitido una mejor gestión del estado y la lógica en los componentes funcionales.

El hook useReducer es una alternativa a useState para manejar estados complejos. Se basa en el patrón de reducción, donde una función "reducer" recibe el estado actual y una acción, y devuelve un nuevo estado. Es una solución ligera en comparación con Redux o Flux y es ideal cuando un componente requiere múltiples actualizaciones de estado basadas en diferentes acciones.

Ejemplo de useReducer

Este es un ejemplo básico de useReducer:

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

El hook useReducer recibe dos argumentos:

  • Reducer: Una función que determina cómo cambiar el estado según la acción recibida.

  • Estado inicial: Puede ser un objeto o una función que retorne un estado inicial.

El dispatch es una función que permite enviar acciones para modificar el estado.

La función reducer

El reducer recibe el estado actual y una acción para generar un nuevo estado:

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

Cada acción es un objeto con una propiedad type, que define la operación a realizar. Si la acción no coincide con ningún caso, el reducer devuelve el estado sin cambios.

¿Por qué usar useReducer?

Cuando un estado es simple, useState suele ser suficiente. Sin embargo, en casos donde hay múltiples acciones que afectan el estado, useReducer ayuda a mantener la lógica organizada y reutilizable.

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 cómo debe verse un contador numérico, y también la lógica de cómo debería sumar una unidad cada vez que se presiona el botón «+1».

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

Pero, ¿qué pasa si necesitamos reutilizar la lógica en otros componentes? Podríamos hablar sobre estados centralizados, pero ¿qué pasa si solo quiero reutilizar la lógica, y que cada componente tenga su propio estado? Una solución menos práctica sería copiar y pegar o exportar funciones desde un archivo separado y hacer que funcionen con el estado de cada componente 😰. Eso no suena conveniente...

La solución a este problema es useReducer, que, como su nombre indica, la función es reducir un estado y su lógica a una unidad reutilizable, permitiendo exportarlo desde un archivo a los componentes que lo necesiten 💪. Este reductor convivirá con el resto de la sintaxis típica de un componente React; puedes aprender más aquí.

Migrando de useState a useReducer

Con useState, un contador con varias acciones se vería como el siguiente ejemplo en el que tenemos un contador que no sólo se incrementa de 1 en 1 sino que además tiene otras opciones para modificar su valor.

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}

Con useReducer, podemos trasladar la lógica a un archivo separado:

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

Y ahora en el componente usamos 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.contador}</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}

Ahora la lógica del estado es completamente reutilizable en otros componentes.

Veamos ambos casos en acción

Todo listo

Hemos visto las ventajas de useReducer y sabemos cómo extraer la lógica de nuestro estado a un reductor ubicado en un fichero externo que otros componentes pueden reutilizar. Esto no significa que tengas que descartar por completo useState y usar sólo useReducer; como todo en programación, se trata de usar la herramienta adecuada para el trabajo adecuado. Puedes aprender más sobre React y sus herramientas en esta categoría.

Los reductores son ideales cuando muchas funciones están asociadas con el estado, y agrupar la lógica y los datos es conveniente. Esto puede ocurrir en un escenario de gran complejidad o cuando funciones y estados necesitan ser reutilizados en múltiples componentes, entonces tendrás la poderosa herramienta de useReducer en tu arsenal.