Patrón reducer + context para estado complejo

Lectura
30 min~5 min lectura
Objetivo de la lección

Concepto clave El patrón reducer + context combina el hook useReducer con Context API para manejar estado complejo de forma predecible y escalable.

Puntos de control
  • Concepto clave
  • Cómo funciona en la práctica
  • Código en acción
  • Errores comunes

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 => 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

  1. 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].
  2. Olvidar el caso default: Siempre retorna el estado actual en default para evitar errores si se despacha una acción desconocida.
  3. Contexto no envuelto: Si usas useCart fuera del CartProvider, obtendrás undefined. Siempre verifica que el contexto exista y lanza un error claro.
  4. Pasar objetos complejos en dispatch: Las acciones deben ser planas y serializables. Evita funciones o referencias circulares.
  5. No memoizar el valor del provider: Si pasas un objeto nuevo cada render, todos los consumidores se re-renderizan. Usa useMemo para 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

  1. Define el estado inicial y el reducer en un archivo todoReducer.js.
  2. Crea el contexto y el proveedor en TodoContext.js.
  3. Exporta un hook useTodo que retorne el estado y dispatch.
  4. Crea un componente TodoApp que use el proveedor y renderice: un formulario para agregar tareas, una lista filtrada y botones de filtro.
  5. Asegúrate de que al agregar una tarea, se genere un id único (puedes usar Date.now()).
  6. Implementa el filtro: si el filtro es 'activas', solo muestra tareas no completadas; si 'completadas', solo las completadas; 'todas' muestra todas.
  7. 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.
Laboratorio de práctica

Antes de marcar esta lección como completa, escribí una evidencia breve para React Intermedio: Dominando Componentes y Patrones Avanzados: un ejemplo, una decisión, una captura, una mini demo o una nota que puedas reutilizar en portfolio.

Reflexión rápida

¿Qué cambiarías en tu forma de trabajar después de aplicar patrón reducer + context para estado complejo?

De lección a portfolio

Convertí esta lección en una prueba técnica visible.

Una app pequeña publicada, con README y decisiones explicadas, funciona mejor que una lista de tecnologías sueltas.

Paso 1

Creá una demo mínima que use el concepto de la lección.

Paso 2

Escribí un README corto con objetivo, stack, decisión técnica y mejora futura.

Paso 3

Publicá la demo y enlazala desde tu perfil profesional.

Newsletter Cursalo

Recibí rutas y cursos nuevos

Sumate para recibir recursos orientados a empleo y portfolio.

  • Rutas de empleo
  • Cursos prácticos
  • Portfolio y entrevistas

Sin spam. También podés entrar con tu cuenta para guardar progreso. Iniciá sesión

Patrón reducer + context para estado complejo | CursaloFalar no WhatsApp