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

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:

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

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

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

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

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

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

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

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

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

Um par de hábitos práticos economiza você de problemas aqui. Use a forma updater (setCount(c => c + 1)) por padrão sempre que o próximo valor constrói sobre o atual, e mantenha a forma simples (setCount(0)) para definir um valor fresco que ignora o antigo. Essa uma regra evita a maioria das surpresas de timing antes de você ter que pensar nelas.

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

Ajuda pensar em state como um snapshot. Para qualquer render individual, count é um valor fixo, decidido quando aquele render começou e inalterado enquanto ele roda. setCount não reatribui aquela variável; ele pede a React para renderizar novamente, e o novo valor apenas aparece no snapshot do próximo render. Então ler state logo depois de defini-lo dá você o valor antigo:

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

É também de onde vêm closures obsoletos. Toda função que você define durante um render, event handlers, effects, timers, fecha sobre os valores de state daquele render. Se uma delas roda depois, um callback setTimeout por exemplo, ela ainda vê o count que ela capturou, mesmo que o valor tenha avançado desde então. A forma updater contorna isso, porque setCount(c => c + 1) pede a React o 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 state que acontecem dentro de um único event handler e re-renderiza uma vez, depois que o handler termina, em vez de depois de cada setCount. É por isso que chamar o setter duas vezes com count + 1 ainda só move o valor em um: ambas as chamadas enfileiram contra o mesmo snapshot antes que qualquer re-render aconteça. As funções updater, em contraste, são enfileiradas e rodam em ordem uma contra a outra, então elas se compõem. Desde React 18 esse batching se estende a atualizações dentro de promises, timeouts, e outros callbacks assíncronos também, então as mesmas regras se aplicam bem além de plain event handlers.

JunoState é a memória de um componente Uma variável comum esquece seu valor toda vez que a função roda, e 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.
JunoState é a memória de um componenteuseState dá você um valor e um setter, e chamar o setter é o que dispara um re-render. Use a forma updater quando o próximo valor depende do atual ou quando você atualiza várias vezes seguidas. State é por instância, então dois do mesmo componente guardam 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 nova cópia do item que você está alterando, tudo mais intocado.
JunoState é a memória de um componente Trate state como um snapshot: fixo pela duração de um render, atualizado apenas no próximo, e é por isso que lê-lo logo depois de defini-lo dá o valor antigo e por que closures capturados ficam obsoletos. React agrupa as atualizações em um event e re-renderiza uma vez, então chamadas de setter contra o mesmo snapshot não se acumulam, enquanto funções updater sim. Quando a correção depende do valor anterior, use a forma updater, e quando o valor inicial é caro de construir, passe a useState a própria função para que React a execute uma vez.

Próximo: Events, onde aqueles handlers onClick que você vem escrevendo recebem uma análise adequada.