useState é o hook que permite a um componente lembrar de algo entre renderizações: uma contagem, o texto de um input, se um menu está aberto. Toda vez que você chama o setter dele, o React renderiza o componente de novo com o novo valor.
Clique no botão e depois troque useState(0) por useState(10) no editor. A prévia é reconstruída e a contagem começa em 10.
A sintaxe
const [value, setValue] = useState(initialValue);
initialValueé usado somente na primeira renderização. Em todas as renderizações seguintes o React o ignora e devolve o valor que guardou.valueé o valor atual para esta renderização.setValuesubstitui o valor e agenda uma nova renderização.
Os nomes são seus. useState retorna um array de dois itens e os colchetes o desempacotam, então [isOpen, setIsOpen] e [email, setEmail] são o mesmo padrão.
Chame useState no nível superior do seu componente, nunca dentro de um loop, de uma condição ou de uma função aninhada. O React associa cada chamada ao seu valor guardado pela ordem das chamadas, então essa ordem precisa ser a mesma em toda renderização (veja as regras dos hooks).
Várias partes de estado
Um componente pode chamar useState quantas vezes precisar. Cada chamada é um valor próprio e independente.
O estado é uma foto
O setter não muda a variável que está nas suas mãos. Ele diz ao React qual deve ser o valor na próxima renderização. Até essa renderização acontecer, a atual continua vendo o valor antigo.
Abra o console abaixo da prévia e clique uma vez. O handler registra 0, o valor antigo, e depois o componente registra uma nova renderização com 1. Não há nada de errado: count é uma constante durante toda a renderização, e a próxima renderização recebe uma nova.
Funções de atualização
Por causa da foto, chamar o setter três vezes com count + 1 soma um, não três: cada chamada calcula 0 + 1. Passe uma função em vez disso e o React entrega a cada chamada o resultado da anterior.
Use a forma com função de atualização sempre que o próximo valor depender do anterior e a atualização puder não partir direto da renderização mais recente: vários sets em um handler, um callback de setInterval ou um efeito.
Objetos e arrays no estado
O React decide se renderiza de novo comparando o valor antigo e o novo com Object.is. Se você altera um objeto no lugar e passa o mesmo objeto de volta, a comparação diz que nada mudou e a tela fica como estava. Sempre passe um novo objeto ou array.
{ ...user, age: user.age + 1 } copia todos os campos e sobrescreve um. [...tags, 'code'] adiciona um item, e filter remove um, os dois retornando um novo array. A página sobre atualizar arrays e objetos trata de objetos aninhados, de editar um item de uma lista e de inserir no meio.
Estado inicial preguiçoso
O argumento de useState é avaliado em toda renderização, mesmo que o React só o use na primeira vez. Quando calculá-lo é caro (ler o localStorage, analisar uma string grande), passe uma função. O React a chama uma vez, na primeira renderização.
// Runs readSavedNotes() on every render, then throws the result away
const [notes, setNotes] = useState(readSavedNotes());
// Runs it once
const [notes, setNotes] = useState(readSavedNotes);
const [notes, setNotes] = useState(() => readSavedNotes());
Reiniciar o estado com uma key
O estado pertence à posição de um componente na árvore. Se você renderiza uma key diferente no mesmo lugar, o React trata como um novo componente e o estado dele recomeça. Essa é a forma mais limpa de reiniciar um formulário quando aquilo que ele edita muda.
Digite uma mensagem e depois troque o destinatário: o rascunho é apagado. Remova key={to} e tente de novo, e o texto continua, porque agora o React vê o mesmo Draft no mesmo lugar.
Erros comuns
Registrar o estado logo depois de defini-lo. Você vê o valor antigo, como mostrado acima. Registre no corpo do componente, ou calcule primeiro o próximo valor em uma variável e use essa variável.
Mutar o estado. user.age++; setUser(user) passa o mesmo objeto, então nada é renderizado. Copie primeiro.
Chamar o setter durante a renderização. <button onClick={setCount(count + 1)}> chama setCount durante a renderização, o que renderiza de novo, o que chama de novo. O React interrompe esse loop com "Too many re-renders". Passe uma função: onClick={() => setCount(count + 1)}.
Guardar valores que você pode calcular. Se fullName é sempre first + ' ' + last, calcule durante a renderização em vez de manter uma terceira parte de estado que pode ficar dessincronizada.
Perguntas frequentes
O que o useState retorna?
Um array com exatamente dois itens: o valor atual e uma função que o substitui. Você mesmo dá os nomes com a desestruturação de arrays, por convenção [thing, setThing].
Por que meu estado não atualiza na hora?
Chamar o setter não muda a variável que você já tem. Ele pede ao React para renderizar o componente de novo, e o novo valor aparece nessa próxima renderização. Dentro do event handler atual o valor antigo ainda está no escopo, então console.log(count) logo depois de setCount(count + 1) imprime o número antigo.
Quando devo passar uma função para o setter?
Quando o novo valor depende do antigo e você o define mais de uma vez, ou de dentro de um timer ou de um efeito. setCount(c => c + 1) sempre parte do valor mais recente, enquanto setCount(count + 1) parte do valor que esta renderização viu.
O useState pode guardar um objeto ou um array?
Sim, mas trate-o como somente leitura. Crie um novo objeto ou array e passe-o para o setter (setUser({ ...user, name }), setItems([...items, item])). Alterar o existente no lugar não dispara uma renderização.
Qual é a diferença entre useState e useReducer?
Os dois armazenam estado. O useState serve para alguns valores independentes. O useReducer leva a lógica de atualização para uma única função que recebe uma ação, o que fica mais legível quando muitos eventos mudam o mesmo estado de formas relacionadas.