Lección 7 de 15 · Lectura y práctica
Patrón reducer + context para estado complejo
Concepto claveEl patrón reducer + context combina el hook useReducer con Context API para manejar estado complejo de forma predecible y escalable. Piensa en un reducer como un semáforo: recibe el estado actual y una acción (como.
Del concepto a la práctica
Taller de esta lección
Explicación
Reducer y context resuelven problemas distintos: el primero define transiciones; el segundo distribuye estado y dispatch. Separarlos permite probar el reducer sin montar componentes.
Ejemplo resuelto
const inicial = { reservas: [], total: 0 };
function reducer(s, a) {
if (a.type === "agregar") return { reservas: [...s.reservas, a.reserva], total: s.total + 1 };
return s;
}
console.log(reducer(inicial, { type: "agregar", reserva: { id: 4 } }).total); // 1El proveedor puede publicar { state, dispatch } al árbol de reservas.
Pruébalo
Define acciones
agregaryquitar.Mantén
totalconsistente con el array.Expón ambos mediante context.
Error frecuente: modificar el array y olvidar total; corrígelo derivando o actualizando ambos en la misma transición.
Comprueba tu resultado
Ejercicio: agrega reservas 4 y 5, quita la 4 y verifica que queda solo 5 y total vale 1. Ejecuta las acciones directamente sobre el reducer.
En quitar, calcula primero el array filtrado y toma total = reservas.length: quitar un id ausente no debe decrementar el total. Decide también si agregar un id duplicado se rechaza. Estas pruebas evitan que un contador duplicado se desincronice del dato que representa.

Mapa de prácticaEl reducer recibe estado y acción y devuelve el próximo estado sin modificar el anterior. TOGGLE de t1 cambia pendiente a completa; la red y otras consecuencias quedan fuera de esa función pura.
Ver la infografía en tamaño completo ↗
Mapa de prácticaEl Provider se sitúa por encima de las pantallas que necesitan el carrito. Catálogo, detalle y carrito leen la misma fuente: dos líneas con cantidades 2 y 1 suman tres unidades, y Context no aporta persistencia por sí solo.
Ver la infografía en tamaño completo ↗Concepto clave
El patrón reducer + context combina el hook useReducer con Context API para manejar estado complejo de forma predecible y escalable. Piensa en un reducer como un semáforo: recibe el estado actual y una acción (como 'cambiar a rojo') y devuelve el nuevo estado. El context actúa como un tablero central que cualquier componente puede consultar sin pasar props manualmente. Este patrón es ideal cuando tienes múltiples valores de estado que cambian juntos (como un carrito de compras o un formulario multi-paso) y necesitas lógica de actualización centralizada.
La clave está en separar la lógica de estado de los componentes. El reducer define cómo cambia el estado en respuesta a acciones, mientras que el context provee el estado y el dispatch a toda la aplicación. Esto evita el 'prop drilling' y hace que los cambios sean trazables, similar a cómo Redux maneja el estado, pero sin librerías externas.
Cómo funciona en la práctica
Imagina que construyes un carrito de compras. El estado incluye: items, cantidad total y precio total. Las acciones son: agregar item, eliminar item, limpiar carrito. En lugar de usar múltiples useState, defines un reducer que maneja estas acciones. Luego, creas un contexto que expone el estado y el dispatch. Los componentes hijos simplemente llaman a dispatch con la acción correspondiente.
Paso a paso: 1) Define el estado inicial y el reducer. 2) Crea el contexto con createContext. 3) En un proveedor, usa useReducer y pasa el estado y dispatch al context. 4) Envuelve tu app con el proveedor. 5) En cualquier componente, usa useContext para acceder al estado o dispatch.
Código en acción
Ejemplo funcional de carrito de compras:
import React, { createContext, useContext, useReducer } from 'react';
const CartContext = createContext();
const initialState = { items: [], totalQuantity: 0, totalPrice: 0 };
function cartReducer(state, action) {
switch (action.type) {
case 'ADD_ITEM': {
const newItems = [...state.items, action.payload];
return {
items: newItems,
totalQuantity: state.totalQuantity + 1,
totalPrice: state.totalPrice + action.payload.price
};
}
case 'REMOVE_ITEM': {
const newItems = state.items.filter(item => item.id !== action.payload.id);
const removedItem = state.items.find(item => item.id === action.payload.id);
return {
items: newItems,
totalQuantity: state.totalQuantity - 1,
totalPrice: state.totalPrice - (removedItem ? removedItem.price : 0)
};
}
case 'CLEAR_CART':
return initialState;
default:
return state;
}
}
export function CartProvider({ children }) {
const [state, dispatch] = useReducer(cartReducer, initialState);
return (
<CartContext.Provider value={{ state, dispatch }}>
{children}
</CartContext.Provider>
);
}
export function useCart() {
const context = useContext(CartContext);
if (!context) throw new Error('useCart must be used within a CartProvider');
return context;
}Uso en un componente:
import { useCart } from './CartContext';
function Product({ product }) {
const { dispatch } = useCart();
return (
<div>
<h3>{product.name}</h3>
<p>${product.price}</p>
<button onClick={() => dispatch({ type: 'ADD_ITEM', payload: product })}>
Agregar al carrito
</button>
</div>
);
}
function CartSummary() {
const { state } = useCart();
return (
<div>
<p>Items: {state.totalQuantity}</p>
<p>Total: ${state.totalPrice.toFixed(2)}</p>
</div>
);
}Errores comunes
- Mutación directa del estado: En el reducer, nunca hagas state.items.push(...). Siempre crea un nuevo array o copia. Ejemplo:
newItems = [...state.items, action.payload]. - Olvidar el caso default: Siempre retorna el estado actual en default para evitar errores si se despacha una acción desconocida.
- Contexto no envuelto: Si usas useCart fuera del CartProvider, obtendrás undefined. Siempre verifica que el contexto exista y lanza un error claro.
- Pasar objetos complejos en dispatch: Las acciones deben ser planas y serializables. Evita funciones o referencias circulares.
- No memoizar el valor del provider: Si pasas un objeto nuevo cada render, todos los consumidores se re-renderizan. Usa
useMemopara estabilizar el valor.
Checklist de dominio
- Define un reducer con estado inicial, acciones y casos para cada tipo de acción.
- Crea un contexto con createContext y un proveedor que use useReducer.
- Exporta un hook personalizado (useCart) que devuelva el contexto y valide su existencia.
- Usa dispatch en componentes para modificar el estado, sin mutar directamente.
- Implementa al menos 3 tipos de acciones (agregar, eliminar, limpiar).
- Evita re-renderizados innecesarios separando contextos si es necesario (ej. estado y dispatch por separado).
- Prueba el flujo completo: agregar items, ver total, eliminar y limpiar.
Crear un gestor de tareas con reducer + context
Objetivo
Construye una aplicación de lista de tareas (TODO) usando el patrón reducer + context. El estado debe incluir: arreglo de tareas (cada tarea con id, texto, completada) y un filtro (todas, activas, completadas). Las acciones: agregar tarea, toggle completada, eliminar tarea, cambiar filtro.
Pasos
- Define el estado inicial y el reducer en un archivo
todoReducer.js. - Crea el contexto y el proveedor en
TodoContext.js. - Exporta un hook
useTodoque retorne el estado y dispatch. - Crea un componente
TodoAppque use el proveedor y renderice: un formulario para agregar tareas, una lista filtrada y botones de filtro. - Asegúrate de que al agregar una tarea, se genere un id único (puedes usar Date.now()).
- Implementa el filtro: si el filtro es 'activas', solo muestra tareas no completadas; si 'completadas', solo las completadas; 'todas' muestra todas.
- Estiliza mínimamente (puedes usar CSS básico, no es necesario framework).
Entregable
Un archivo TodoApp.jsx que exporte el componente principal, y los archivos auxiliares todoReducer.js y TodoContext.js. La aplicación debe ser funcional y permitir agregar, marcar como completada, eliminar y filtrar tareas.
Mini-rúbrica de evaluación
- Reducer maneja correctamente 4 acciones: ADD_TODO, TOGGLE_TODO, DELETE_TODO, SET_FILTER (25%).
- Contexto provee estado y dispatch, y el hook useTodo valida el contexto (25%).
- Componente TodoApp usa el contexto y muestra la lista filtrada correctamente (25%).
- No hay mutaciones directas del estado; se usan operadores inmutables como spread o filter (15%).
- El código es legible y está organizado en archivos separados (10%).
Pistas
- Para el filtro, guarda un string 'all', 'active', 'completed' en el estado. En el selector, usa filter según ese valor.
- Usa useReducer dentro del proveedor y pasa [state, dispatch] como valor del contexto. No olvides memoizar el valor si es necesario.
- Para eliminar una tarea, usa filter con el id. Para toggle, mapea el arreglo y cambia la propiedad completed del elemento coincidente.
Sigue con React Intermedio: Dominando Componentes y Patrones Avanzados
Esta lección forma parte de un curso de Cursalo. Revisa su programa y elige entre la compra individual o el acceso a toda la escuela.
Si ya tenías acceso a este curso, entra con tu cuenta: tus derechos y tu progreso se conservan. No necesitas comprarlo de nuevo.