Hook useReducer
Gestiona transiciones de estado complejas con una función reductora y acciones despachadas.
Busca en todas las páginas de la documentación
Gestiona transiciones de estado complejas con una función reductora y acciones despachadas.
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥
Tarjeta de referencia rápida - lista para copiar y pegar.
const [state, dispatch] = useReducer(reducer, initialState);
// Con inicializador perezoso
const [state, dispatch] = useReducer(reducer, initialArg, init);
// Despachar una acción
dispatch({ type: "increment" });
dispatch({ type: "setName", payload: "Alice" });Cuándo usarlo: Tu estado tiene múltiples sub-valores, las transiciones dependen del estado anterior, o deseas centralizar la lógica de estado para capacidad de prueba.
"use client";
import { useReducer } from "react";
type State = { count: number; step: number };
type Action =
| { type: "increment" }
| { type: "decrement" }
| { type: "setStep"; payload: number }
| { type: "reset" };
const initialState: State = { count: 0, step: 1 };
function reducer(state: State, action: Action): State {
switch (action.type) {
case "increment":
return { ...state, count: state.count + state.step };
case "decrement":
return { ...state, count: state.count - state.step };
case "setStep":
return { ...state, step: action.payload };
case "reset":
return initialState;
default:
return state;
}
}
export function StepCounter() {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<div className="space-y-3">
<div className="flex items-center gap-4">
<button onClick={() => dispatch({ type: "decrement" })} className="px-3 py-1 border rounded">
−
</button>
<span className="text-xl font-mono w-16 text-center">{state.count}</span>
<button onClick={() => dispatch({ type: "increment" })} className="px-3 py-1 border rounded">
+
</button>
</div>
<label className="flex items-center gap-2 text-sm">
Paso:
<input
type="number"
value={state.step}
onChange={(e) => dispatch({ type: "setStep", payload: Number(e.target.value) })}
className="w-16 border rounded px-2 py-1"
/>
</label>
<button onClick={() => dispatch({ type: "reset" })} className="text-sm text-blue-600 underline">
Reiniciar
</button>
</div>
);
}Lo que esto demuestra:
count y step)dispatch es estable entre renderizados y seguro de pasar a componentes hijos sin useCallbackuseReducer acepta una función reductora pura (state, action) => newState y un estado inicialdispatch(action) envía la acción a través del reductora y provoca un re-renderizado con el nuevo estadodispatch es estable - nunca cambia entre renderizadosuseState, React agrupa múltiples envíos dentro del mismo manejador de evento en un único re-renderizado| Parámetro | Tipo | Descripción |
|---|---|---|
reducer | (state: S, action: A) => S | Función pura que calcula el nuevo estado a partir del estado actual y la acción |
initialArg | S o I | Estado inicial, o argumento pasado a la función init |
init | (initialArg: I) => S | Función inicializadora perezosa opcional |
| Retorno | Tipo | Descripción |
|---|---|---|
state | S | Valor de estado actual |
dispatch | (action: A) => void | Función para enviar acciones al reductora |
Con inicializador perezoso:
function init(initialCount: number): State {
return { count: initialCount, step: 1 };
}
const [state, dispatch] = useReducer(reducer, 0, init);Reductora con Immer para actualizaciones más limpias:
import { useImmerReducer } from "use-immer";
function reducer(draft: State, action: Action) {
switch (action.type) {
case "addTodo":
draft.todos.push({ id: Date.now(), text: action.payload, done: false });
break;
case "toggleTodo":
const todo = draft.todos.find((t) => t.id === action.payload);
if (todo) todo.done = !todo.done;
break;
}
}Empareja con contexto para estado global:
const StateContext = createContext<State>(initialState);
const DispatchContext = createContext<Dispatch<Action>>(() => {});
export function AppProvider({ children }: { children: ReactNode }) {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<StateContext.Provider value={state}>
<DispatchContext.Provider value={dispatch}>
{children}
</DispatchContext.Provider>
</StateContext.Provider>
);
}// Unión discriminada para acciones - TypeScript estrecha dentro de casos switch
type Action =
| { type: "add"; payload: string }
| { type: "remove"; payload: number }
| { type: "clear" };
// useReducer genérico recoge tipos automáticamente
const [state, dispatch] = useReducer(reducer, initialState);
// dispatch se tipifica como Dispatch<Action>Mutar el estado directamente - Modificar state.count++ dentro del reductora no provocará un re-renderizado y corromperá tu estado. Solución: Siempre retorna un nuevo objeto: { ...state, count: state.count + 1 }.
Efectos secundarios en el reductora - Obtener datos o escribir en localStorage dentro del reductora rompe el modelo de renderizado de React. Solución: Mantén el reductora puro; ejecuta efectos secundarios en useEffect o manejadores de eventos.
Olvidar el caso predeterminado - Si se despacha una acción no reconocida y no hay un caso predeterminado que retorne state, obtendrás undefined. Solución: Siempre incluye default: return state en tu switch.
Exceso de ingeniería del estado simple - Usar useReducer para un booleano o número único añade complejidad innecesaria. Solución: Usa useState para valores simples e independientes.
| Alternativa | Úsalo Cuando | No lo Uces Cuando |
|---|---|---|
useState | Uno o dos valores de estado independientes | Múltiples valores relacionados con transiciones complejas |
| Zustand | Estado compartido entre muchos componentes con selectores | El estado es local a un árbol de componentes |
| XState | Necesitas máquinas de estado formal con guardias y transiciones | Operaciones CRUD simples |
useActionState (React 19) | Las transiciones de estado están vinculadas a envíos de formularios | Gestión general de estado del lado del cliente |
¿Por qué no siempre usar useReducer? Para un interruptor único o contador, useState es más simple y legible. Utiliza useReducer cuando tengas 3+ valores de estado relacionados o el siguiente estado dependa tanto del estado actual como de la carga de una acción.
useReducer centraliza transiciones de estado relacionadas en una única función pura.reducer(state, action) y afirma el resultado.useState independientes.dispatch una vez y retorna la misma referencia en cada renderizado.dispatch de manera segura a componentes hijos sin useCallback.React.memo no se re-renderizarán debido a que dispatch cambie.state.count++ modifica el objeto existente sin crear una nueva referencia.Object.is para detectar cambios, así que no verá la mutación y no se re-renderizará.{ ...state, count: state.count + 1 }.useEffect o manejadores de eventos que despachen acciones.type Action =
| { type: "add"; payload: string }
| { type: "remove"; payload: number }
| { type: "clear" };
function reducer(state: State, action: Action): State {
switch (action.type) {
case "add":
// action.payload es string aquí
return { ...state, items: [...state.items, action.payload] };
case "remove":
// action.payload es number aquí
return { ...state, items: state.items.filter((_, i) => i !== action.payload) };
case "clear":
return { ...state, items: [] };
}
}useReducer(reducer, initialArg, init) es una función que calcula el estado inicial a partir de initialArg.useReducer.default: return state, el reductora retorna undefined.undefined, rompiendo el componente.default: return state como red de seguridad.useState para uno o dos valores independientes (un interruptor, un contador).useReducer cuando tengas 3+ valores relacionados, transiciones complejas, o desees lógica de estado probada.useReducer es más claro.import { useImmerReducer } from "use-immer";
function reducer(draft: State, action: Action) {
switch (action.type) {
case "addTodo":
draft.todos.push({ id: Date.now(), text: action.payload, done: false });
break;
case "toggleTodo":
const todo = draft.todos.find(t => t.id === action.payload);
if (todo) todo.done = !todo.done;
break;
}
}import { createContext, Dispatch } from "react";
const DispatchContext = createContext<Dispatch<Action>>(() => {});
// Los consumidores obtienen un dispatch tipificado:
// dispatch({ type: "add", payload: "item" }) -- OK
// dispatch({ type: "unknown" }) -- Error de TypeScriptuseReducer para gestión de estado globaluseReducer en un hook personalizado para encapsular lógica de dominioRevisado por Chris St. John·Última actualización: 16 jul 2026
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥