Skip to content
This page has been auto-translated and may contain errors.View in English

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:

jsx
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:

jsx
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:

jsx
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:

jsx
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:

jsx
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:

jsx
// 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:

jsx
// 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á:

jsx
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:

jsx
// 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.

Alguns hábitos práticos economizam problemas aqui. Recorra à forma atualizadora (setCount(c => c + 1)) por padrão sempre que o próximo valor se constrói no atual, e mantenha a forma simples (setCount(0)) para definir um valor fresco que ignora o antigo. Essa única regra evita a maioria das surpresas de timing antes de você ter que pensar nelas.

Mantenha o estado mínimo também. Armazene apenas o que você não consegue calcular a partir de algo que você já tem. Se você mantém uma lista de itens em estado, não mantenha também sua contagem em estado; leia-a com items.length enquanto renderiza. Toda peça extra de estado é outro valor para manter sincronizado, e estado duplicado é exatamente onde duas cópias se afastam. Quando vários valores sempre mudam juntos, um único objeto ou um hook useReducer geralmente é mais estável que um espalhado de chamadas useState separadas.

Ajuda pensar em estado como um snapshot. Para qualquer renderização única, count é um valor fixo, decidido quando essa renderização começou e inalterado enquanto ela roda. setCount não reatribui essa variável; pede a React para renderizar novamente, e o novo valor só aparece no snapshot da próxima renderização. Então ler estado logo após defini-lo dá o valor antigo:

jsx
function handleClick() {
  setCount(count + 1)
  console.log(count) // ainda o valor desta renderização, não o novo
}

Isto também é de onde vêm closures obsoletos. Toda função que você define durante uma renderização, manipuladores de eventos, efeitos, timers, fecha sobre os valores de estado daquela renderização. Se um deles roda depois, um callback de setTimeout por exemplo, ele ainda vê o count que capturou, mesmo que o valor tenha avançado desde então. A forma atualizadora evita isso, porque setCount(c => c + 1) pede a React pelo valor atual no momento em que roda em vez de confiar no capturado.

Batching é a outra metade do quadro. React agrupa as atualizações de estado que acontecem dentro de um único manipulador de evento e renderiza uma vez, depois que o manipulador termina, em vez de após cada setCount. É por isso que chamar o setter duas vezes com count + 1 ainda apenas move o valor por um: ambas as chamadas se enfileiram contra o mesmo snapshot antes de qualquer renderização acontecer. As funções atualizadoras, por contraste, são enfileiradas e rodam em ordem uma contra a outra, então elas se compõem. Desde React 18 este batching estende para atualizações dentro de promises, timeouts, e outros callbacks async também, então as mesmas regras se aplicam bem além de simples manipuladores de eventos.

JunoEstado é a memória de um componente Uma variável simples esquece seu valor toda vez que a função roda, e 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.

JunoEstado é a memória de um componenteuseState te dá um valor e um setter, e chamar o setter é o que dispara uma re-renderização. Use a forma atualizadora quando o próximo valor depende do atual ou quando você atualiza várias vezes seguidas.

Estado é por instância, então dois do mesmo componente mantêm valores separados, e você sempre passa ao setter um novo valor em vez de mutar o array ou objeto antigo. Para uma lista, isso significa map: uma cópia nova do item que você está mudando, tudo mais intocado.

JunoEstado é a memória de um componente Trate estado como um snapshot: fixo pela duração de uma renderização, atualizado apenas na próxima, o que é por que lê-lo logo após defini-lo dá o valor antigo e por que closures capturados ficam obsoletos.

React agrupa as atualizações em um evento e renderiza uma vez, então chamadas de setter contra o mesmo snapshot não se empilham, enquanto funções atualizadoras fazem.

Quando a correção depende do valor anterior, recorra à forma atualizadora, e quando o valor inicial é caro para construir, passe a useState a função em si para que React a rode uma vez.

Próximo: Events, onde aqueles manipuladores onClick que você vem escrevendo recebem uma olhada apropriada.