Gerencie o estado complexo de formulários multi-etapas com useReducer - ações explícitas como SET_FIELD, NEXT_STEP e VALIDATE tornam as transições de estado previsíveis, testáveis e fáceis de depurar sem a necessidade de bibliotecas externas.
Quando usar isso: Quando seu formulário tem 3+ etapas, validação entre etapas ou transições de estado complexas o suficiente para que o gerenciamento de várias chamadas useState se torne propenso a erros.
// Nomes de campo fortemente tipados - captura erros de digitação em tempo de compilaçãointerface SignupFields { name: string; email: string; plan: "free" | "pro" | "enterprise";}type SetFieldAction = { [K in keyof SignupFields]: { type: "SET_FIELD"; name: K; value: SignupFields[K] };}[keyof SignupFields];// Agora dispatch({ type: "SET_FIELD", name: "plan", value: "invalid" }) é um erro de tipo// Contexto de dispatch tipado para evitar prop drillingimport { createContext, useContext, type Dispatch } from "react";const FormDispatchContext = createContext<Dispatch<FormAction> | null>(null);function useFormDispatch(): Dispatch<FormAction> { const dispatch = useContext(FormDispatchContext); if (!dispatch) throw new Error("useFormDispatch must be used within FormProvider"); return dispatch;}
Estado desatualizado em callbacks assíncronos - Se você ler state dentro de um setTimeout ou await, obterá o valor no momento do dispatch, não o mais recente. Correção: Use useRef para espelhar o estado atual ou despache uma ação do callback assíncrono em vez de ler o estado.
O spread de objeto cria apenas cópias rasas - Objetos aninhados (como fields dentro de state) também devem ser espalhados: { ...state, fields: { ...state.fields, [name]: value } }. Correção: Sempre espalhe em todos os níveis de aninhamento que você está modificando, ou use produce do Immer.
Esquecer de limpar erros ao alterar um campo - O usuário corrige o erro, mas a mensagem permanece. Correção: Limpe o erro do campo específico dentro de SET_FIELD, como mostrado no exemplo funcional.
O reducer deve ser puro - Sem chamadas de API, sem leituras de localStorage, sem Date.now() dentro do reducer. Correção: Execute efeitos colaterais no componente ou manipulador de eventos, em seguida, despache o resultado.
A validação é executada contra o estado desatualizado após o dispatch - dispatch não atualiza state de forma síncrona. Chamar dispatch({ type: "SET_FIELD" }) e depois ler state.fields retorna o valor antigo. Correção: Valide usando o valor que você está prestes a despachar, não o state atual.
Reducers grandes se tornam difíceis de ler - Uma instrução switch de 200 linhas é pior do que 8 chamadas useState. Correção: Extraia os manipuladores de casos em funções nomeadas: case "SET_FIELD": return handleSetField(state, action);.
Caso default ausente no switch - O TypeScript não avisará se você esquecer um caso, a menos que adicione verificação exaustiva. Correção: Adicione default: { const _exhaustive: never = action; return state; } para capturar ações não tratadas em tempo de compilação.