Estado
Uma variável comum dentro de uma função é esquecida no momento em que a função termina, então ela não consegue manter um valor que muda com o tempo e aparece na tela. Essa lacuna é o que estado preenche: é como um componente lembra de algo entre renderizações. Enquanto props são dados passados de fora que um componente nunca muda, estado é dados que o componente possui e atualiza a si mesmo. O hook useState dá esse memória a um componente, e te dá um jeito de atualizar o valor que também avisa React para redesenhar.
Aqui está um contador:
import { useState } from 'react'
function Counter() {
const [count, setCount] = useState(0)
return <button onClick={() => setCount(count + 1)}>{count}</button>
}useState(0) define o valor inicial e retorna um par: o valor atual, count, e uma função para alterá-lo, setCount. Os nomes são seus; a forma [value, setValue] é a convenção. O onClick do botão executa uma função cada vez que é clicado, que o capítulo Events cobre completamente em seguida, então confie por enquanto. Essa função chama setCount(count + 1), e a chamada faz duas coisas. Armazena o novo valor, e renderiza Counter novamente para que a tela mostre o número atualizado. Chamar o setter é o sinal para React de que algo mudou e o componente deve rodar novamente.
Atualizando baseado no valor anterior
Você verá frequentemente o setter chamado com uma função em vez de um valor simples:
setCount(c => c + 1)Esta é a forma atualizadora. React chama sua função com o valor mais recente e usa o que você retorna como o próximo estado. Use-a sempre que o novo valor é construído a partir do antigo, e especialmente quando você atualiza mais de uma vez seguida. Estas duas linhas não fazem o que parecem fazer:
setCount(count + 1)
setCount(count + 1)Ambas as leituras de count veem o mesmo valor desta renderização, então juntas apenas aumentam a contagem em um. A forma atualizadora conserta isso, porque cada chamada recebe o resultado da anterior:
setCount(c => c + 1)
setCount(c => c + 1)Agora a contagem sobe em dois. Inverter um booleano funciona da mesma forma e é onde você encontrará a forma atualizadora com mais frequência: setIsOpen(open => !open) toma o valor atual e retorna o oposto. Quando seu próximo valor depende do atual, a forma atualizadora é o padrão seguro.
Estado é por instância de componente
Cada vez que você renderiza um componente, ele recebe seu próprio estado separado. Dois contadores na mesma página mantêm cada um sua própria contagem:
function App() {
return (
<>
<Counter />
<Counter />
</>
)
}Clicar no primeiro botão não afeta o segundo. Eles compartilham o mesmo código Counter, e cada um mantém um valor independente. O estado pertence a cada instância na tela, então uma função pode impulsionar muitas cópias separadas.
Substitua o estado, não o mute
Você atualiza o estado dando a React um novo valor, e React compara esse valor com o antigo para decidir o que mudou. Mudar o valor existente no lugar pula esse sinal, então a tela fica obsoleta. Isto importa mais com objetos e arrays. Para adicionar a um array, construa um novo array em vez de fazer push no antigo:
// Pula a atualização: React nunca vê um novo valor
todos.push(newTodo)
setTodos(todos)
// Funciona: um array totalmente novo
setTodos(prev => [...prev, newTodo])O mesmo vale para objetos: espalhe o antigo em um novo objeto com sua mudança aplicada, e retorne isso do atualizador. Trate o valor que você recebe de useState como somente leitura, e sempre passe ao setter algo novo.
Aninhamento exige cuidado. Espalhar um objeto copia seu nível superior, então um objeto aninhado dentro dele ainda é o mesmo objeto, e mudar esse aninhado volta ao original:
// Muta o original: o objeto de endereço aninhado ainda é compartilhado com o novo
user.address.city = 'Brasília'
setUser({ ...user })
// Funciona: um novo objeto em cada nível até a mudança
setUser(prev => ({
...prev,
address: { ...prev.address, city: 'Brasília' }
}))Os parênteses ao redor do objeto fazem a função de seta retorná-lo em vez de lê-lo como um bloco de código. Cada nível até a mudança precisa do seu próprio spread, o que fica complicado conforme o aninhamento se aprofunda. Quando você se vê escrevendo uma corrente deles, geralmente é um sinal de que o estado quer ser achatado em peças menores.
Alterando um item em uma lista
Alterar um item que já está em um array é onde as pessoas mais frequentemente ficam presas. A ferramenta para isso é map. Percorra o array, retorne uma cópia alterada do item que você quer, e retorne todo outro item como está:
function toggleTodo(id) {
setTodos(prev => prev.map(todo =>
todo.id === id ? { ...todo, done: !todo.done } : todo
))
}O setter toma a forma atualizadora, já que o próximo array é construído a partir do atual. O ternário faz a seleção: o todo correspondente recebe uma cópia com done invertido, e tudo mais passa direto. Esses itens que passam direto são os mesmos objetos que sempre foram, o que é bom. Apenas o que você mudou precisa ser novo, junto com o array que os contém, e map constrói esse array fresco para o setter.
Remover um item é filter, e adicionar um é o spread da seção anterior. Entre os três, a maioria das atualizações de lista é coberta.
Quando o valor inicial é caro
JavaScript avalia o argumento que você passa para useState em cada renderização, antes de useState em si rodar, e React mantém o resultado apenas na primeira vez. Para um valor como 0, isso não custa nada. Para uma chamada de função que faz trabalho real, ela roda a cada renderização e o resultado é descartado após a primeira:
// buildBoard() roda em cada renderização, e seu resultado é descartado após a primeira
const [board, setBoard] = useState(buildBoard())
// buildBoard é chamada uma vez, na primeira renderização
const [board, setBoard] = useState(buildBoard)A primeira linha chama a função e passa a React o resultado. A segunda passa a React a função em si e deixa React chamá-la uma vez. Reserve isso para trabalho que é genuinamente custoso, como ler do armazenamento, gerar uma estrutura grande, ou uma computação pesada. Em useState(0) é ruído.
Nota de versão
Em class components, estado vivia em this.state e você atualizava com this.setState. useState é o equivalente de function component, e é o jeito padrão de manter estado em React moderno.
useState a lembra. Você recebe o valor e um setter, e chamar esse setter tanto armazena o novo valor quanto redesenha o componente. Quando o novo valor se constrói no antigo, use setCount(c => c + 1) para estar sempre trabalhando do mais recente. Quando esse valor é um array ou objeto, dê ao setter um totalmente novo com sua mudança aplicada.
Próximo: Events, onde aqueles manipuladores onClick que você vem escrevendo recebem uma olhada apropriada.

