Hook useState
Gerencie o estado local do componente com o hook mais fundamental do React.
Busque em todas as páginas da documentação
Gerencie o estado local do componente com o hook mais fundamental do React.
🤖 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 [value, setValue] = useState<T>(initialValue)
// Com inicializador preguiçoso (cálculo caro)
const [value, setValue] = useState(() => computeExpensive())
// Função de atualizador (quando o novo estado depende do anterior)
setValue(prev => prev + 1)Quando usar: Você precisa de estado local e síncrono em um único componente.
"use client";
import { useState } from "react";
export function Counter() {
const [count, setCount] = useState(0);
return (
<div className="flex items-center gap-4">
<button
onClick={() => setCount(prev => prev - 1)}
className="px-3 py-1 border rounded"
>
-
</button>
<span className="text-xl font-mono w-12 text-center">{count}</span>
<button
onClick={() => setCount(prev => prev + 1)}
className="px-3 py-1 border rounded"
>
+
</button>
</div>
);
}O que isso demonstra:
useState básico com um númeroprev => prev + 1 em vez de setCount(count + 1) para evitar problemas de closure obsoletocount mudauseState retorna uma tupla: o valor de estado atual e uma função de configuraçãoinitialValuesetState dentro do mesmo manipulador de eventos em uma única re-renderização para otimização de desempenho| Parâmetro | Tipo | Descrição |
|---|---|---|
initialValue | T ou () => T | Valor inicial do estado, ou uma função que o retorna (inicializador preguiçoso) |
| Retorno | Tipo | Descrição |
|---|---|---|
value | T | Valor atual do estado |
setValue | (value: T) => void ou (prev: T) => T | Atualizador de estado - aceita um novo valor ou uma função atualizadora |
Estado de objeto:
const [form, setForm] = useState({ name: "", email: "" });
// Deve usar spread para criar nova referência
setForm(prev => ({ ...prev, name: "Alice" }));Estado de array:
const [items, setItems] = useState<string[]>([]);
setItems(prev => [...prev, "new item"]);Inicializador preguiçoso (executa apenas na montagem):
const [data, setData] = useState(() => {
return JSON.parse(localStorage.getItem("key") ?? "null");
});// O tipo é inferido do valor inicial
const [count, setCount] = useState(0); // number
// Genérico explícito para tipos de união ou nulo
const [user, setUser] = useState<User | null>(null);
// Genérico explícito para tipos complexos
const [items, setItems] = useState<Item[]>([]);Coisas que vão te morder. Cada armadilha inclui o que dá errado, por que acontece e a correção.
Armadilha de closure obsoleto - Ler count dentro de um setTimeout ou useEffect sem ele no array de dependências fornece o valor antigo. Correção: Use a função atualizadora setCount(prev => prev + 1).
Identidade do objeto - setState({ ...obj }) cria uma nova referência a cada vez, mesmo que os valores não tenham mudado, causando re-renderizações desnecessárias. Correção: Espalhe apenas quando os valores realmente mudarem, ou use useMemo para valores derivados.
Armadilha do inicializador preguiçoso - Passar computeExpensive() em vez de () => computeExpensive() executa a função em cada renderização, não apenas na primeira. Correção: Sempre envolva cálculos caros em uma função de seta.
Armadilha de agrupamento - Chamar setCount(count + 1) três vezes seguidas resulta em apenas +1, não +3, porque cada chamada lê o mesmo count obsoleto. Correção: Use a função atualizadora setCount(prev => prev + 1).
Outras maneiras de resolver o mesmo problema - e quando cada uma é a melhor escolha.
| Alternativa | Use Quando | Não Use Quando |
|---|---|---|
useReducer | Transições de estado são complexas ou dependem do estado anterior | Toggle simples ou valor único |
| Loja Zustand | O estado é compartilhado entre muitos componentes não relacionados | O estado é local a um componente |
| Parâmetros de busca da URL | O estado deve sobreviver à atualização da página e ser compartilhável | Atualizações de alta frequência (digitação, arrastar) |
useRef | Você precisa de um valor mutável que não acione re-renderizações | Você precisa que a UI reflita o valor |
Por que não usar sempre Zustand? Zustand adiciona uma dependência e indireção. useState tem custo zero para estado local - sem provider, sem loja, sem seletores. Use a ferramenta mais simples que funcionar.
De uma aplicação SaaS de produção Next.js 15 / React 19 (SystemsArchitect.io).
// Exemplo de produção: formulário de edição de FAQ com múltiplos useState
// Arquivo: src/components/admin/faq-edit-form.tsx
'use client';
import { useState } from 'react';
interface FaqEditFormProps {
faq: Faq & { category?: { id: string; slug: string; title: string } };
onSave: (updatedFaq: Partial<Faq>) => Promise<void>;
onCancel: () => void;
}
export default function FaqEditForm({ faq, onSave, onCancel }: FaqEditFormProps) {
const [question, setQuestion] = useState(faq.question);
const [answer, setAnswer] = useState(faq.answer);
const [isActive, setIsActive] = useState(faq.isActive);
const [sortOrder, setSortOrder] = useState(faq.sortOrder);
const [isSaving, setIsSaving] = useState(false);
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setIsSaving(true);
try {
await onSave({ question, answer, isActive, sortOrder });
} catch (error) {
console.error('Erro ao salvar FAQ:', error);
} finally {
setIsSaving(false);
}
};
// ... JSX do formulário
}O que isso demonstra em produção:
useState. Nenhum genérico explícito é necessário para useState(faq.question), pois faq.question já é do tipo string.finally garante que isSaving seja redefinido para false em caso de sucesso e falha. Sem ele, uma falha ao salvar deixaria o formulário preso em um estado de carregamento.Partial<Faq> significa que apenas os campos alterados são enviados para o manipulador de salvamento, não o objeto FAQ inteiro. Isso mantém a chamada da API enxuta.useState separadas funcionam bem para um formulário deste tamanho. Para formulários com mais de 6-8 campos, considere useReducer ou uma biblioteca de formulários como react-hook-form para reduzir o boilerplate e habilitar a validação em nível de campo.isSaving é usada para desabilitar o botão de envio durante a operação assíncrona, evitando o duplo envio.setCount(count + 1) várias vezes no mesmo manipulador de eventos, cada chamada lê o mesmo valor obsoleto de count.prev => prev + 1 sempre recebe o estado pendente mais recente, então três chamadas resultam em +3 em vez de +1.setTimeout, useEffect e funções assíncronas.useState: useState(() => expensiveComputation()).localStorage ou analisar grandes quantidades de dados.setState dentro do mesmo manipulador de eventos em uma única re-renderização.setTimeout, promessas e manipuladores de eventos nativos (agrupamento automático).setA(1); setB(2); resulta em uma re-renderização, não duas.const [form, setForm] = useState({ name: "", email: "" });
// Correto: usa spread para criar uma nova referência
setForm(prev => ({ ...prev, name: "Alice" }));
// Errado: mutando o objeto existente
form.name = "Alice"; // Nenhuma re-renderizaçãoObject.is para comparar o estado antigo e o novo.{ ...obj }), criará uma nova referência, o que aciona uma re-renderização mesmo que os valores sejam idênticos.useState(computeExpensive()) chama a função em cada renderização e usa o resultado apenas na primeira.useState(() => computeExpensive()) chama a função apenas na primeira renderização.// Use um genérico explícito para tipos de união
const [user, setUser] = useState<User | null>(null);
// Mais tarde, o TypeScript sabe que user pode ser nulo
if (user) {
console.log(user.name); // estreitado para User
}// Sem o genérico, o TypeScript infere never[]
const [items, setItems] = useState<string[]>([]);
// Agora você pode adicionar strings
setItems(prev => [...prev, "new item"]);useState para valores simples e independentes (um toggle, um contador, uma entrada única).useReducer quando você tiver 3 ou mais valores de estado relacionados, ou quando o próximo estado depender tanto do estado atual quanto de um payload de ação.useReducer ou uma biblioteca de formulários reduz o boilerplate.setState durante a renderização agenda uma nova renderização, que chama setState novamente, criando um loop infinito.useState são mais simples e evitam spreads desnecessários quando apenas um valor muda.{ x, y }).useReducer em vez de qualquer uma das abordagens.Revisado por Chris St. John·Última atualização: 16 de jul. de 2026
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥