Busque em todas as páginas da documentação
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥
// Máquina de estado simples com useReducer
type FetchState =
| { status: "idle" }
| { status: "loading" }
| { status: "success"; data: unknown }
| { status: "error"; error: Error };
type FetchEvent =
| { type: "FETCH" }
| { type: "RESOLVE"; data: unknown }
| { type: "REJECT"; error: Error }
| { type: "RESET" };
function fetchReducer(state: FetchState, event: FetchEvent): FetchState {
switch (state.status) {
case "idle":
if (event.type === "FETCH") return { status: "loading" };
return state;
case "loading":
if (event.type === "RESOLVE") return { status: "success", data: event.data };
if (event.type === "REJECT") return { status: "error", error: event.error };
return state;
case "error":
if (event.type === "FETCH") return { status: "loading" };
if (event.type === "RESET") return { status: "idle" };
return state;
case "success":
if (event.type === "FETCH") return { status: "loading" };
if (event.type === "RESET") return { status: "idle" };
return state;
}
}
const [state, send] = useReducer(fetchReducer, { status: "idle" });Quando usar isso: Quando um componente tem múltiplos estados com transições restritas (por exemplo, um formulário de várias etapas, fluxo de trabalho assíncrono ou modal com estados de carregamento/erro/sucesso). Se você se encontrar gerenciando múltiplas booleanas como isLoading, isError, isSuccess, use uma máquina de estado em vez disso.
import { useReducer, useCallback, type ReactNode } from "react";
// --- Máquina de estado de formulário de várias etapas ---
interface FormData {
name: string;
email: string;
plan: string;
}
type FormState =
| { step: "details"; data: Partial<FormData> }
| { step: "plan"; data: Partial<FormData> }
| { step: "review"; data: FormData }
| { step: "submitting"; data: FormData }
| { step: "complete"; data: FormData }
| { step: "error"; data: FormData; error: string };
type FormEvent =
| { type: "NEXT"; fields: Partial<FormData> }
| { type: "BACK" }
| { type: "SUBMIT" }
| { type: "SUCCESS" }
| { type: "FAIL"; error: string }
| { type: "RETRY" };
function formReducer(state: FormState, event: FormEvent): FormState {
switch (state.step) {
case "details":
if (event.type === "NEXT") {
return { step: "plan", data: { ...state.data, ...event.fields } };
}
return state;
case "plan":
if (event.type === "NEXT") {
const data = { ...state.data, ...event.fields } as FormData;
return { step: "review", data };
}
if (event.type === "BACK") return { step: "details", data: state.data };
return state;
case "review":
if (event.type === "SUBMIT") return { step: "submitting", data: state.data };
if (event.type === "BACK") return { step: "plan", data: state.data };
return state;
case "submitting":
if (event.type === "SUCCESS") return { step: "complete", data: state.data };
if (event.type === "FAIL") {
return { step: "error", data: state.data, error: event.error };
}
return state;
case "error":
if (event.type === "RETRY") return { step: "submitting", data: state.data };
if (event.type === "BACK") return { step: "review", data: state.data };
return state;
case "complete":
return state; // Estado terminal
}
}
function SignupWizard() {
const [state, send] = useReducer(formReducer, {
step: "details",
data: {},
});
const handleSubmit = useCallback(async () => {
send({ type: "SUBMIT" });
try {
await fetch("/api/signup", {
method: "POST",
body: JSON.stringify(state.step === "review" ? state.data : null),
});
send({ type: "SUCCESS" });
} catch (err) {
send({ type: "FAIL", error: (err as Error).message });
}
}, [state]);
switch (state.step) {
case "details":
return (
<DetailsStep
data={state.data}
onNext={(fields) => send({ type: "NEXT", fields })}
/>
);
case "plan":
return (
<PlanStep
data={state.data}
onNext={(fields) => send({ type: "NEXT", fields })}
onBack={() => send({ type: "BACK" })}
/>
);
case "review":
return (
<ReviewStep
data={state.data}
onSubmit={handleSubmit}
onBack={() => send({ type: "BACK" })}
/>
);
case "submitting":
return <LoadingSpinner message="Criando sua conta..." />;
case "error":
return (
<ErrorDisplay
error={state.error}
onRetry={() => send({ type: "RETRY" })}
onBack={() => send({ type: "BACK" })}
/>
);
case "complete":
return <SuccessMessage data={state.data} />;
}
}O que isso demonstra:
isLoading && isErroruseReducer é o primitivo nativo do React para estados. A função reducer É a máquina de estado.switch em state.step (ou state.status) atua como o diagrama de estados; verificações if aninhadas em event.type definem transições válidas.| Conceito | Implementação | Propósito |
|---|---|---|
| Estado | Union discriminada | Representa todos os estados possíveis com dados associados |
| Evento | Union de { type: string; ... } | Todas as entradas possíveis que acionam transições |
| Reducer | (state, event) => state | Função pura definindo a lógica da máquina de estado |
| Dispatch | send(event) | Aciona uma transição de estado |
Condições de guarda - permitem transições apenas quando as condições são atendidas:
case "details":
if (event.type === "NEXT") {
if (!event.fields.name || !event.fields.email) {
return { ...state, validationError: "Todos os campos são obrigatórios" };
}
return { step: "plan", data: { ...state.data, ...event.fields } };
}
return state;Usando XState para máquinas complexas - quando a lógica de estado excede o que useReducer lida de forma limpa:
import { useMachine } from "@xstate/react";
import { createMachine, assign } from "xstate";
const toggleMachine = createMachine({
id: "toggle",
initial: "inactive",
context: { count: 0 },
states: {
inactive: {
on: {
TOGGLE: {
target: "active",
actions: assign({ count: ({ context }) => context.count + 1 }),
},
},
},
active: {
on: { TOGGLE: "inactive" },
},
},
});
function Toggle() {
const [state, send] = useMachine(toggleMachine);
return (
<button onClick={() => send({ type: "TOGGLE" })}>
{state.value} (alternado {state.context.count} vezes)
</button>
);
}step, status) permite o estreitamento de tipo.switch exaustivo. O TypeScript avisará se você esquecer um estado quando o tipo de retorno for especificado.boolean em objetos de estado - eles criam 2^n estados possíveis. Use estados nomeados explícitos em vez disso.typegen para inferência de tipos de evento.Sopa de booleanas - Usar isLoading, isError, hasData como booleanas separadas cria combinações impossíveis como isLoading && isError. Correção: Substitua por um único estado de union discriminada.
Efeitos colaterais no reducer - Reducers devem ser funções puras. Chamadas de API ou mutações de DOM dentro do reducer quebram as regras do React e causam comportamento imprevisível. Correção: Acione efeitos colaterais fora do reducer com base nas transições de estado (em handlers de evento ou effects).
Esquecer o retorno padrão - Se o reducer não tratar um evento em um determinado estado e não retornar state, o estado se torna undefined. Correção: Sempre adicione return state como o padrão para eventos não tratados em cada caso de estado.
Exagero de engenharia para estado simples - Um toggle que está ligado ou desligado não precisa de uma máquina de estado. Correção: Use useState(false) para cenários triviais de dois estados. Recorra a máquinas quando você tiver 3+ estados ou transições complexas.
| Abordagem | Trade-off |
|---|---|
Máquina de estado useReducer | Nativo, sem dependências; lógica de transição manual |
| XState | Poderoso, ferramentas visuais; dependência extra, curva de aprendizado |
Múltiplas booleanas useState | Simples para 1-2 estados; estados impossíveis se tornam possíveis |
| Zustand com campo de estado | Bom para máquinas de estado globais; não específico do React |
useActionState (React 19) | Projetado para fluxos de submissão de formulário; limitado a ações de formulário |
useReducer é o primitivo nativo do React para implementar máquinas de estado.type FetchState =
| { status: "idle" }
| { status: "loading" }
| { status: "success"; data: unknown }
| { status: "error"; error: Error };data só existe em success).switch no discriminante (status), prevenindo o acesso a campos inválidos.isLoading && isError tornam-se irrepresentáveis.isLoading, isError, isSuccess cria combinações impossíveis.useState(false) é suficiente.switch externo em state.step (ou state.status) define em qual estado você está.if aninhadas em event.type definem quais transições são válidas nesse estado.return state padrão ignora eventos que não são válidos para o estado atual.useEffect com base nas transições de estado.undefined, o que pode travar o componente ou causar bugs silenciosos.return state como o caso padrão para cada estado na instrução switch.case "details":
if (event.type === "NEXT") {
if (!event.fields.name || !event.fields.email) {
return { ...state, validationError: "Todos os campos são obrigatórios" };
}
return { step: "plan", data: { ...state.data, ...event.fields } };
}
return state;never no caso padrão para capturar estados não tratados em tempo de compilação.useReducer é suficiente e não tem dependências.n campos booleanos criam 2^n combinações possíveis, a maioria das quais são inválidas.{ isLoading: true, isError: true } é um estado impossível que booleanas permitem."idle" | "loading" | "error" | "success".useEffect que observe o estado.SUCCESS ou FAIL com base na resposta da API.Revisado por Chris St. John·Última atualização: 16 de jul. de 2026
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥