//
Busque em todas as páginas da documentação
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥
Tipa o estado do seu componente corretamente com useState e useReducer. Lide com valores simples, objetos complexos, estado anulável e reducers de união discriminada.
// useState simples - o tipo é inferido
const [count, setCount] = useState(0); // number
const [name, setName] = useState(""); // string
const [enabled, setEnabled] = useState(false); // boolean// Tipagem explícita para estado complexo ou anulável
type User = {
id: string;
name: string;
email: string;
};
const [user, setUser] = useState<User | null>(null);
// Mais tarde...
if (user) {
console.log(user.name); // TypeScript sabe que user não é null aqui
}// useReducer com ações de união discriminada
type CounterState = {
count: number;
lastAction: string;
};
type CounterAction =
| { type: "increment"; payload: number }
| { type: "decrement"; payload: number }
| { type: "reset" };
function counterReducer(state: CounterState, action: CounterAction): CounterState {
switch (action.type) {
case "increment":
return { count: state.count + action.payload, lastAction: "increment" };
case "decrement":
return { count: state.count - action.payload, lastAction: "decrement" };
case "reset":
return { count: 0, lastAction: "reset" };
}
}
function Counter() {
const [state, dispatch] = useReducer(counterReducer, { count: 0, lastAction: "none" });
return (
<div>
<p>Count: {state.count}</p>
<button onClick={() => dispatch({ type: "increment", payload: 1 })}>+1</button>
<button onClick={() => dispatch({ type: "reset" })}>Reset</button>
</div>
);
}useState infere o tipo do valor inicial. useState(0) retorna [number, Dispatch<SetStateAction<number>>].null mas eventualmente contendo um objeto), você deve fornecer um parâmetro de tipo explícito: useState<User | null>(null).useReducer infere os tipos de estado e ação a partir da assinatura da função reducer. Definir o reducer com tipos de parâmetro explícitos oferece segurança de tipo completa tanto no corpo do reducer quanto nas chamadas dispatch.type) permitem que o TypeScript restrinja o tipo da ação dentro de cada ramo case, dando acesso a campos específicos do ramo, como payload.Inicializador preguiçoso:
const [data, setData] = useState<Map<string, User>>(() => new Map());Estado com uma tupla:
const [coords, setCoords] = useState<[number, number]>([0, 0]);useReducer com Immer:
import { useImmerReducer } from "use-immer";
function reducer(draft: CounterState, action: CounterAction) {
switch (action.type) {
case "increment":
draft.count += action.payload;
draft.lastAction = "increment";
break;
case "reset":
draft.count = 0;
draft.lastAction = "reset";
break;
}
}
const [state, dispatch] = useImmerReducer(reducer, { count: 0, lastAction: "none" });SetStateAction<T> é T | ((prev: T) => T). É por isso que tanto setCount(5) quanto setCount(prev => prev + 1) funcionam.useReducer, o tipo de retorno do reducer deve corresponder ao tipo do estado. O TypeScript impõe isso automaticamente quando você anota os parâmetros do reducer.as com estado. Se o TypeScript reclamar, geralmente significa que seus tipos precisam de ajuste, não de um type assertion.useState<User>() sem um valor inicial resulta em User | undefined, não User. Sempre forneça um valor inicial ou digite explicitamente como User | undefined.setUser sem espalhar o estado anterior substitui o objeto inteiro. O TypeScript detectará campos obrigatórios ausentes, o que na verdade é útil.any anula o propósito. Mesmo para formas dinâmicas, use Record<string, unknown> ou um tipo adequado.null em estado anulável leva a erros de tempo de execução que o TypeScript tenta prevenir por meio de verificações estritas de null.| Abordagem | Prós | Contras |
|---|---|---|
useState com inferência | Zero boilerplate para valores simples | Não pode expressar estados iniciais anuláveis ou de união |
useState<T> genérico explícito | Controle total sobre o tipo de estado | Um pouco mais verboso |
useReducer | Transições de estado previsíveis, ótimo para estado complexo | Mais boilerplate que useState |
| Loja Zustand | Estado compartilhado com inferência TypeScript | Dependência externa |
useActionState (React 19) | Gerenciamento de estado de formulário integrado | Limitado a padrões de formulário/ação |
useState(0) infere number, useState("") infere string.useState<User | null>(null).useState<[number, number]>([0, 0]).SetStateAction<T> é definido como T | ((prev: T) => T).setCount(5) quanto setCount(prev => prev + 1) são válidos.type: { type: "increment"; payload: number } | { type: "reset" }.case, dando acesso a campos específicos do ramo como payload.const [data, setData] = useState<Map<string, User>>(() => new Map());User | undefined, não User.User | undefined.useState substituem o valor inteiro. Ao contrário do setState de componentes de classe, não há mesclagem.setUser(prev => ({ ...prev, ...newPartialData })).Record<string, unknown> ou defina um tipo adequado.case retornar uma forma que falte um campo obrigatório, você receberá um erro em tempo de compilação.import { useImmerReducer } from "use-immer";
function reducer(draft: CounterState, action: CounterAction) {
switch (action.type) {
case "increment":
draft.count += action.payload;
break;
}
}useImmerReducer infere tipos da assinatura do reducer, assim como useReducer.useReducer é integrado, limitado a um componente e não tem dependências.useState<User | null>(null).if (user) { user.name }.Revisado por Chris St. John·Última atualização: 7 de jul. de 2026
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥