State
Uma variável comum dentro de uma função é esquecida no momento em que a função termina, então ela não consegue guardar um valor que muda com o tempo e aparece na tela. Esse é o espaço que o state preenche: é como um componente se lembra de algo entre renders. Enquanto props são dados passados de fora que um componente nunca altera, state é dados que o componente possui e atualiza a si mesmo. O hook useState dá ao componente essa memória, e oferece a você 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 devolve 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, o 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. Ela armazena o novo valor, e re-renderiza Counter para que a tela mostre o número atualizado. Chamar o setter é o sinal para React que algo mudou e o componente deve executar novamente.
Atualizando baseado no valor anterior
Você verá frequentemente o setter sendo chamado com uma função em vez de um valor simples:
setCount(c => c + 1)Esta é a forma updater. React chama sua função com o valor mais recente e usa o que você retorna como o próximo state. 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:
setCount(count + 1)
setCount(count + 1)Ambas as leituras de count veem o mesmo valor deste render, então juntas elas aumentam a contagem apenas em um. A forma updater 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ê vai encontrar a forma updater com mais frequência: setIsOpen(open => !open) pega o valor atual e retorna seu oposto. Quando seu próximo valor depende do atual, a forma updater é a escolha segura.
State é por instância de componente
Cada vez que você renderiza um componente, ele recebe seu próprio state separado. Dois contadores na mesma página cada um guarda 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 de Counter, e cada um mantém um valor independente. O state pertence a cada instância na tela, então uma função pode controlar muitas cópias separadas.
Substitua state, não o mute
Você atualiza state dando a React um novo valor, e React compara esse valor ao antigo para decidir o que mudou. Alterar o valor existente no lugar pula esse sinal, então a tela fica desatualizada. Isso 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 completamente novo
setTodos(prev => [...prev, newTodo])O mesmo vale para objetos: espalhe o antigo em um novo objeto com sua alteração aplicada, e retorne isso do updater. Trate o valor que você recebe de useState como somente leitura, e sempre passe ao setter algo novo.
Aninhamento precisa de cuidado. Espalhar um objeto copia seu nível superior, então um objeto aninhado dentro dele ainda é o mesmo objeto, e alterar aquele aninhado volta ao original:
// Muta o original: o objeto address aninhado ainda é compartilhado com o novo
user.address.city = 'Brasília'
setUser({ ...user })
// Funciona: um novo objeto em cada nível até a alteração
setUser(prev => ({
...prev,
address: { ...prev.address, city: 'Brasília' }
}))Os parênteses em volta do objeto são o que fazem a arrow function retorná-lo em vez de lê-lo como um bloco de código. Cada nível até a alteração precisa de seu próprio spread, o que fica complicado conforme o aninhamento se aprofunda. Quando você se vê escrevendo uma cadeia deles, geralmente é um sinal que o state quer ser achatado em pedaços menores.
Alterando um item em uma lista
Alterar um item que já está em um array é onde as pessoas mais ficam presas. A ferramenta para isso é map. Caminhe pelo array, retorne uma cópia alterada para o item que está procurando, 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 recebe a forma updater, 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 é tudo bem. Apenas o que você alterou 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 está coberta.
Quando o valor inicial é caro
JavaScript avalia o argumento que você passa para useState em todo render, antes de useState próprio 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 todo render e o resultado é descartado após a primeira:
// buildBoard() roda em todo render, e seu resultado é descartado após o primeiro
const [board, setBoard] = useState(buildBoard())
// buildBoard é chamado uma vez, no primeiro render
const [board, setBoard] = useState(buildBoard)A primeira linha chama a função e passa a React o resultado. A segunda passa a React a própria função e deixa React chamá-la uma vez. Guarde isso para trabalho genuinamente caro, como ler do armazenamento, gerar uma estrutura grande, ou uma computação pesada. Em useState(0) é apenas ruído.
Nota de versão
Em class components, o state vivia em this.state e você o atualizava com this.setState. useState é o equivalente de function component, e é a forma padrão de manter state no React moderno.
useState a lembra. Você recebe o valor e um setter, e chamar aquele setter tanto armazena o novo valor quanto redesenha o componente. Quando o novo valor constrói sobre o antigo, use setCount(c => c + 1) para sempre estar trabalhando do mais recente. Quando aquele valor é um array ou objeto, dê ao setter um completamente novo com sua alteração aplicada. Próximo: Events, onde aqueles handlers onClick que você vem escrevendo recebem uma análise adequada.

