Hook useReducer
Gerencie transições de estado complexas com uma função reducer e ações despachadas.
Busque em todas as páginas da documentação
Gerencie transições de estado complexas com uma função reducer e ações despachadas.
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥
Cartão de receita de referência rápida - pronto para copiar e colar.
const [state, dispatch] = useReducer(reducer, initialState);
// Com inicializador preguiçoso
const [state, dispatch] = useReducer(reducer, initialArg, init);
// Despacha uma ação
dispatch({ type: "increment" });
dispatch({ type: "setName", payload: "Alice" });Quando usar isso: Seu estado tem múltiplos sub-valores, as transições dependem do estado anterior, ou você quer centralizar a lógica de estado para testabilidade.
"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">
Step:
<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">
Reset
</button>
</div>
);
}O que isso demonstra:
count e step)dispatch é estável entre renders e seguro para passar para filhos sem useCallbackuseReducer aceita uma função reducer pura (state, action) => newState e um estado inicialdispatch(action) envia a ação através do reducer e dispara uma re-renderização com o novo estadodispatch é estável - ela nunca muda entre rendersuseState, o React agrupa múltiplos dispatches dentro do mesmo manipulador de eventos em uma única re-renderização| Parâmetro | Tipo | Descrição |
|---|---|---|
reducer | (state: S, action: A) => S | Função pura que calcula o novo estado a partir do estado atual e da ação |
initialArg | S or I | Estado inicial, ou argumento passado para a função init |
init | (initialArg: I) => S | Função inicializadora preguiçosa opcional |
| Retorno | Tipo | Descrição |
|---|---|---|
state | S | Valor do estado atual |
dispatch | (action: A) => void | Função para enviar ações ao reducer |
Com inicializador preguiçoso:
function init(initialCount: number): State {
return { count: initialCount, step: 1 };
}
const [state, dispatch] = useReducer(reducer, 0, init);Reducer com Immer para atualizações mais limpas:
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;
}
}Emparelhar com context 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ão discriminada para ações - TypeScript restringe dentro de casos switch
type Action =
| { type: "add"; payload: string }
| { type: "remove"; payload: number }
| { type: "clear" };
// O useReducer genérico pega os tipos automaticamente
const [state, dispatch] = useReducer(reducer, initialState);
// dispatch é tipado como Dispatch<Action>Mutar o estado diretamente - Modificar state.count++ dentro do reducer não acionará uma re-renderização e corromperá seu estado. Correção: Sempre retorne um novo objeto: { ...state, count: state.count + 1 }.
Efeitos colaterais no reducer - Buscar dados ou escrever no localStorage dentro do reducer quebra o modelo de renderização do React. Correção: Mantenha o reducer puro; execute efeitos colaterais em useEffect ou manipuladores de eventos.
Esquecer o caso padrão - Se uma ação não reconhecida for despachada e não houver um caso padrão que retorne state, você obtém undefined. Correção: Sempre inclua default: return state em seu switch.
Exagerar no estado simples - Usar useReducer para um único booleano ou número adiciona complexidade desnecessária. Correção: Use useState para valores simples e independentes.
| Alternativa | Usar Quando | Não Usar Quando |
|---|---|---|
useState | Um ou dois valores de estado independentes | Múltiplos valores relacionados com transições complexas |
| Zustand | Estado compartilhado entre muitos componentes com seletores | Estado é local para uma árvore de componentes |
| XState | Você precisa de máquinas de estado formais com guardas e transições | Operações CRUD simples |
useActionState (React 19) | Transições de estado vinculadas a envios de formulário | Gerenciamento de estado geral do lado do cliente |
Por que não usar sempre useReducer? Para um único toggle ou contador, useState é mais simples e legível. Use useReducer quando você tiver 3+ valores relacionados ou o próximo estado depender tanto do estado atual quanto de um payload de ação.
useReducer centraliza transições de estado relacionadas em uma única função pura.reducer(state, action) e afirmar o resultado.useState.dispatch uma vez e retorna a mesma referência em cada renderização.dispatch com segurança para componentes filhos sem useCallback.React.memo não serão re-renderizados devido à mudança de dispatch.state.count++ modifica o objeto existente sem criar uma nova referência.Object.is para detectar mudanças, então ele não verá a mutação e não re-renderizará.{ ...state, count: state.count + 1 }.useEffect ou manipuladores de eventos que despacham ações.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 é string aqui
return { ...state, items: [...state.items, action.payload] };
case "remove":
// action.payload é number aqui
return { ...state, items: state.items.filter((_, i) => i !== action.payload) };
case "clear":
return { ...state, items: [] };
}
}useReducer(reducer, initialArg, init) é uma função que calcula o estado inicial a partir de initialArg.useReducer.default: return state, o reducer retorna undefined.undefined, quebrando o componente.default: return state como uma rede de segurança.useState para um ou dois valores independentes (um toggle, um contador).useReducer quando você tiver 3+ valores relacionados, transições complexas, ou quiser lógica de estado testável.useReducer é mais 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>>(() => {});
// Consumidores obtêm um dispatch tipado:
// dispatch({ type: "add", payload: "item" }) -- OK
// dispatch({ type: "unknown" }) -- Erro do TypeScriptuseReducer para gerenciamento de estado globaluseReducer em um hook customizado para encapsular a lógica de domínioRevisado por Chris St. John·Última atualização: 16 de jul. de 2026
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥