Alfonso Neil Jiménez Casallas
Miguel Sosa
Raul Arias
Cuando un componente de React empieza a manejar varias acciones a la vez, useState se queda corto. Aquí entra useReducer, el hook ideal para estados complejos con múltiples transiciones, como un contador con incremento, decremento o cualquier lógica que dependa del tipo de acción.
Esta guía te muestra cómo configurar useReducer paso a paso, con un ejemplo práctico de contador en Vite y React, pensado para quienes ya dominan los estados básicos y buscan escalar su lógica.
¿Qué es useReducer y por qué usarlo en lugar de useState?
useReducer es un hook de React que centraliza la lógica de actualización de estado en una función llamada reducer. En vez de tener varios setters dispersos, defines un único lugar donde decides cómo cambia el estado según la acción recibida.
¿Cuándo conviene usar useReducer en vez de useState? Cuando tu componente maneja más de dos acciones sobre el mismo estado, valores anidados o transiciones que dependen del estado anterior. Si solo alternas un true/false, useState basta.
¿Cómo se prepara el proyecto con Vite y React?
Antes de escribir el hook, necesitas un proyecto limpio. El flujo es directo y replicable:
npm create vite@latest y selecciona React con JavaScript.npm install.npm run dev.src/components y dentro un archivo Counter.jsx.Luego, en App.jsx, importa el componente Counter y elimina el contenido por defecto que trae la plantilla. Así dejas el lienzo listo para enfocarte solo en el hook.
¿Cómo se construye la función reducer paso a paso?
El reducer es una función pura que recibe dos argumentos: el estado actual y una acción. Dentro, un switch decide qué hacer según el tipo de acción.
La estructura base luce así:
jsx import { useReducer } from 'react';
const reducer = (state, action) => { switch (action.type) { case 'increment': return { count: state.count + 1 }; case 'decrement': return { count: state.count - 1 }; default: return state; } };
Cada case representa una transición posible. El valor 'increment' o 'decrement' lo defines tú, no es palabra reservada. El default devuelve el estado sin cambios cuando la acción no coincide con ningún caso, evitando comportamientos inesperados.
¿Qué papel cumplen state y action dentro del reducer?
El primer argumento, state, es un objeto con los valores actuales, en este ejemplo { count: 0 }. El segundo, action, es otro objeto que viaja desde el componente con al menos una propiedad type, que indica qué transición ejecutar.
Esta separación es clave: el componente no modifica el estado directamente, solo despacha una intención. La función reducer es la única responsable de calcular el nuevo estado.
¿Cómo se conecta useReducer al componente Counter?
Dentro del componente, el hook se invoca pasando el reducer y el estado inicial. Devuelve un arreglo con el estado actual y una función dispatch para enviar acciones.
jsx const [state, dispatch] = useReducer(reducer, { count: 0 });
A partir de aquí, el JSX muestra state.count y los botones disparan acciones con dispatch({ type: 'increment' }) o dispatch({ type: 'decrement' }). El render se actualiza automáticamente cuando el reducer devuelve un nuevo estado.
¿Qué hace dispatch exactamente? Dispatch envía un objeto de acción al reducer. No modifica el estado por sí mismo; solo notifica qué tipo de cambio se desea. El reducer interpreta la acción y produce el nuevo estado.
¿Cómo se ven los botones que disparan las acciones?
El componente final integra visualización y eventos en pocas líneas:
jsx return (
<div> <p>Contador: {state.count}</p> <button onClick={() => dispatch({ type: 'increment' })}>increment</button> <button onClick={() => dispatch({ type: 'decrement' })}>decrement</button> </div> );Al probarlo en el navegador, verás el contador iniciado en cero y dos botones que aumentan o reducen el valor. La lógica vive en un solo lugar, lo que facilita agregar más casos sin ensuciar el componente.
¿Cómo escalar el reducer a más operaciones?
El mismo patrón sirve para cualquier acción adicional. Solo agregas un case nuevo al switch y un botón que despache ese tipo:
case 'multiply': return { count: state.count * 2 };.case 'divide': return { count: state.count / 2 };.case 'reset': return { count: 0 };.Esta extensibilidad es la razón por la que useReducer brilla en formularios complejos, carritos de compra, filtros múltiples o cualquier componente con varias transiciones sobre el mismo estado.
El beneficio real aparece cuando dejas de pensar en setters sueltos y empiezas a razonar en términos de acciones: cada interacción del usuario se traduce en una intención clara, y el reducer decide cómo afecta al estado.
¿Qué otro caso se te ocurre para aplicar useReducer en tus proyectos? Cuéntalo en los comentarios.
Alfonso Neil Jiménez Casallas
Miguel Sosa
Raul Arias
Fernando Andres Carreño Rodriguez
James Yesid Herrera Acevedo
Jorge Arias Argüelles
Raúl Adolfo Sánchez Rodríguez
Edwin Einsen Vásquez Velásquez
Moises Hernandez
David Palacio
Platzi Team
Juan Diego Mejia Maestre
Platzi Team
Jorge Luis Rueda Marin
Miguel Cruz
Gerardo J. Rito
Eduardo Segovia
Leandro Gonzalez
Platzi Team
Yan Harold Muñoz Dominguez
Leonardo Caraballo
Diego Frank Lipa Choque
Raúl Adolfo Sánchez Rodríguez
Johan Sebastian
Andres Silva Vega
Valentina Llorente Hernández
Daniel Alberto Vega Bejarano
Para casos mucho más complejos en manejo de estados, es recomendable aprender sobre Redux, RxJS y otras opciones.
Totalmente, esta clase es precisamente un preámbulo a redux, ya que redux usa useReducer para determinar los cambios de estado en las stores.
Totalmente, aunque hoy por hoy diria que en lugar de ir aprender Redux de una, se aprenda zustand (menos boilerplate), y aprender Redux en caso de que sea estrictamente necesario, lo menciono desde mi experiencia, llevo 2 años en laburo y hasta el momento solo he usado el mismo context de react y zustand, por mas que he recomendado redux, siempre lo descartan por lo antes mencionado jeje, saludos.
Guía que sin dudas les ayudara muchísimo a entender desde cero el hook useReducer, en React paso a paso. 🚀
Muchas gracias compañero, excelente recurso
Al utilizar el hook useReducer, el segundo argumento que se debe pasar es el estado inicial. En el contexto del contador, debes proporcionar un objeto que contenga la propiedad count, por ejemplo:
const initialState = { count: 0 }; const [state, dispatch] = useReducer(reducer, initialState);
Esto establece el valor inicial del contador en cero.
Me parece que se podria explicar mejor la lógica, no solo narrar lo que se va tipeando.
¿En qué parte no explica lo que va haciendo?
Se entiende el concepto, pero creo que durante todas este curso deberían enseñar como organizar bien nuestro código y sobre todo el principio de separación de responsabilidades. Estamos usando super malas practicas
Según la descripción del curso, dice que el nivel es básico. Si buscas cómo organizar un proyecto puedes consultar sobre arquitectura. Cuando se dicta un curso a nivel básico, se requiere abundar en las bases y conceptos, no en buenas prácticas (por cierto, un tema muy extenso).
No se le puede enseñar a un bebé a caminar si primero no gatea. Mira el siguiente post, creo que es buena guía.
La inmutabilidad en programación se refiere a la propiedad de un objeto que no puede ser modificado después de su creación. En el contexto de React, esto es importante porque permite optimizaciones en el rendimiento y facilita la gestión de estados. Por ejemplo, en lugar de modificar un objeto directamente, se crea una nueva versión del objeto con los cambios deseados. Esto ayuda a React a detectar cambios en el estado y a re-renderizar componentes de manera eficiente.
En el desarrollo con useReducer, es fundamental entender este concepto para manejar correctamente los estados complejos.
⚙️ useReducer en React: Manejo de Estados Complejos
📌 ¿Qué es useReducer?
Hook de React diseñado para manejar estados complejos con múltiples acciones o transformaciones. Es como Redux pero integrado directamente en React.
🆚 Diferencia clave:
useState: Reemplaza el estado anterior → Ideal para estados simplesuseReducer: Define cómo debe cambiar el estado según acciones → Ideal para lógica compleja🎯 ¿Cuándo usar useReducer?
✅ El estado tiene estructura compleja (objetos anidados, arrays)
✅ Las actualizaciones dependen del valor anterior
✅ Necesitas manejar múltiples acciones relacionadas
✅ La lógica de actualización es compleja o extensa
📐 Estructura Básica
const [state, dispatch] = useReducer(reducer, initialState);
Componentes:
state: Estado actualdispatch: Función para enviar accionesreducer: Función que actualiza el estado según la accióninitialState: Valor inicial del estado💻 Ejemplo: Contador con useReducer
1️⃣ Función Reductora
const reducer = (state, action) => { switch (action.type) { case 'increment': return { count: state.count + 1 }; case 'decrement': return { count: state.count - 1 }; default: return state; } };
🔑 Patrón: switch-case para manejar diferentes tipos de acciones ⚠️ Importante: Cada caso retorna un nuevo objeto (inmutabilidad)
2️⃣ Componente Completo
import { useReducer } from 'react'; const Counter = () => { const reducer = (state, action) => { switch (action.type) { case 'increment': return { count: state.count + 1 }; case 'decrement': return { count: state.count - 1 }; default: return state; } }; const [state, dispatch] = useReducer(reducer, { count: 0 }); return ( <div> Contador: {state.count} <button onClick={() => dispatch({ type: 'increment' })}> Increment </button> <button onClick={() => dispatch({ type: 'decrement' })}> Decrement </button> </div> ); };
🔄 Flujo de Datos
dispatch con un objeto de acción: { type: 'increment' }reducerreducer determina el nuevo estado💡 Flujo unidireccional → Código predecible y fácil de depurar
✨ Ventajas de useReducer vs useState
VentajaDescripción
🎯 Centralización
Toda la lógica en un solo lugar (función reductora)
🔗 Estados relacionados
Ideal para múltiples valores de estado conectados
🧪 Testeable
Función reductora pura → fácil de probar aisladamente
📖 Legibilidad
Acciones con nombres descriptivos
🐛 Depuración
Flujo de datos predecible y rastreable
📈 Escalabilidad
Perfecto para aplicaciones grandes
🎓 Conclusión
useReducer proporciona una estructura más organizada y mantenible para estados complejos. La clave está en:
✔️ Definir claramente las acciones posibles
✔️ Especificar cómo cada acción transforma el estado
✔️ Mantener la inmutabilidad del estado
✔️ Usar nombres descriptivos para las acciones
🚀 Ideal para: Formularios complejos, carritos de compra, gestión de listas con múltiples operaciones, estados con lógica de negocio compleja
En el switch, se utiliza return { count: state.count + 1 } para incrementar el contador, lo que significa que se está creando un nuevo objeto con el mismo formato que el estado inicial, pero con el valor de count incrementado en 1. Esto permite actualizar el estado de manera adecuada.
En el caso del default, se usa return state para devolver el estado sin cambios si ninguna de las acciones coincide. Esto es esencial para mantener la inmutabilidad del estado y asegurar que React pueda detectar cambios correctamente y re-renderizar el componente.
Gracias, me ayudó tu aclaración
La función dispatch envía un objeto de 'acción' al reducer, que es una función pura que toma el estado actual y la acción, y calcula un nuevo estado. El reducer utiliza un switch para determinar cómo actualizar el estado basándose en el type de la acción, retornando siempre un nuevo estado sin modificar el original.
gracias, me quedo mas claro
useReducer es como tener un administrador que se encarga de recordar y actualizar la información importante en tu aplicación, asegurándose de que todo esté organizado y funcione correctamente. Es especialmente útil cuando tienes mucha información que puede cambiar de muchas maneras diferentes
El state.count se declara en el estado inicial que pasas al useReducer. En el ejemplo del componente Counter, el estado inicial es un objeto con la propiedad count, inicializada en cero:
const [state, dispatch] = useReducer(reducer, { count: 0 });
El reducer maneja las acciones y modifica el estado. En este caso, state.count refleja el valor actual del contador, que se incrementa o decrementa según la acción despachada. De esta manera, state.count es parte del estado que defines al utilizar useReducer.
Un poco de conteo
El código de Counter.jsx es
import { useReducer } from 'react'; //Creamos un switch que nos va a permitir cambiar de estados const reducer = (state, action) => { switch (action.type) { case "increment": return {count:state.count + 1} break; case "decrement": return {count:state.count - 1} break; default: return state; } } const Counter = () => { const [state,dispatch]=useReducer(reducer, { count :0 }) return ( <div> <p>Contador: {state.count}</p> <button onClick={() => dispatch({ type: "increment" })}>+</button> <br /> <button onClick={() => dispatch({ type: "decrement" })}>- <br /> </button> </div> ) } export default Counter;
en que casos uso useReduce
Hola, Diego Frank. Mira, piensa en useState como un interruptor de luz: simple, directo, hace una cosa. Pero cuando tu estado se vuelve un laberinto —muchos valores relacionados que cambian juntos o lógica de actualización que parece un nudo gordiano— ahí entra useReducer.
Úsalo cuando la lógica de tu estado sea predecible pero compleja. Si tienes múltiples sub-valores que dependen uno del otro, o si te encuentras escribiendo demasiados useState que se actualizan siempre en conjunto, es señal de que necesitas un reducer.
Básicamente, centralizas la lógica de cambio en una función, separando el qué quieres hacer (la acción) del cómo cambia el estado (el reducer). Es menos código desordenado y más control sobre las transiciones.
¿Te ha pasado que terminas con un componente lleno de funciones set que se llaman una tras otra para actualizar un mismo objeto?
Hola! Hice este video explicando el hook UseReducer, espero les sea de ayuda! A mi me costó al principio entenderlo un poco. Ahora a ponerlo en práctica!
Yo lo pude entender así:
const [variable_estado, funcion_que_invoca_reduce_y_recibe_criterio_de_cambio_de_estado] = useReducer(funcion_que_implementa_cambio_estado, estado_inicial); const funcion_que_implementa_cambio_estado(estado, parametro_dispatch)
import { useReducer } from "react"; const reducer = (state, action) =>{ switch(action.type){ case "Increment": return {count: state.count + 1 }; case "Decrement": return {count: state.count - 1 }; default: return state; } } const Counter = () =>{ const [state, dispatch ] = useReducer(reducer, {count: 0}); return( <div className="grid max-w-full mt-10 w-80 h-60 m-15 bg-purple-200 shadow-2xl rounded-2xl place-content-center hover:shadow-pink-300 text-pink-900 "> <h1 className="text-2xl">Counter with useReducer</h1> <p className="text-2xl font-sans"> {state.count}</p> <div className="grid grid-cols-2 m-2"> <button onClick={() => dispatch({type: "Increment"})} className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded-2xl" >Increment</button> <button onClick={() => dispatch({type: "Decrement"})} className="bg-red-500 hover:bg-red-700 text-white font-bold py-2 px-4 rounded-2xl" >Decrement</button> </div> </div> ) } export default Counter; ````import { useReducer } from "react";` `const reducer = (state, action) =>{ switch(action.type){ case "Increment": return {count: state.count + 1 }; case "Decrement": return {count: state.count - 1 }; default: return state; }}const Counter = () =>{ const [state, dispatch ] = useReducer(reducer, {count: 0});` ` return( <div className="grid max-w-full mt-10 w-80 h-60 m-15 bg-purple-200 shadow-2xl rounded-2xl place-content-center hover:shadow-pink-300 text-pink-900 "> <h1 className="text-2xl">Counter with useReducer</h1> <p className="text-2xl font-sans"> {state.count}</p> <div className="grid grid-cols-2 m-2"> <button onClick={() => dispatch({type: "Increment"})} className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded-2xl" >Increment</button>` ` <button onClick={() => dispatch({type: "Decrement"})} className="bg-red-500 hover:bg-red-700 text-white font-bold py-2 px-4 rounded-2xl" >Decrement</button> </div> </div> )}` `export default Counter;`
Coun useReducer podemos crear una lista de pendientes por hacer e ir seleccionando los que ya hemos realizado y los que no los tendremos como pendientes (to do).