Artigo original: How to become a pro with React setState() in 10 minutes

O público-alvo deste artigo são aqueles que já tiveram seu primeiro contato com React e que, por serem iniciantes, têm dúvidas sobre como o setState funciona e como usá-lo corretamente. Ele também deverá ajudar desenvolvedores plenos ou sêniores a usar formas mais limpas e abstratas de definição de um state, bem como a fazer com que as funções de ordem superior tratem dos states e os abstraiam.

Apenas leia e se divirta! Pegue uma xícara de café e siga lendo!

Conceitos básicos do setState( )

Os componentes em React permitem a você dividir a interface do usuário (UI) em partes independentes e reutilizáveis, de modo que você possa pensar cada parte isoladamente.

Conceitualmente, os componentes são como as funções em JavaScript. Elas aceitam entradas arbitrárias (chamadas de "props") e retornam elementos em React, que descrevem o que deve aparecer na tela.

Se você precisar dar ao usuário a oportunidade de inserir algo ou, de algum modo, de alterar as variáveis que o componente está recebendo como props, precisará usar o setState.

Independentemente de declarar um componente como função ou como classe, ele nunca deve modificar suas próprias props.

Todos os componentes do React devem agir como funções puras no que diz respeito às suas props. Isso quer dizer que as funções nunca tentam alterar suas entradas e sempre retornam o mesmo resultado para as mesmas entrada.

Claro, as UIs da aplicação são dinâmicas e se alteram com o tempo. É por isso que o state foi criado.

State permite que os componentes do React alterem seu resultado com o passar do tempo em resposta às ações dos usuários, respostas da rede e todo o resto, sem violar esta regra.

Componentes definidos como classes têm alguns recursos adicionais. State local é um recurso disponível apenas para componentes de classe.

setState é o método de API fornecido com a biblioteca de modo que o usuário possa definir e manipular states com o passar do tempo.

Três regras práticas ao usar setState()

Não modifique o state diretamente

fDk0J1KHkNJKyjha9jV2Ic5VtMHtx0hX54-5
Maneira errada (acima) e certa (abaixo) de se definir um state

Atualizações do state podem ser assíncronas

O React pode fazer várias chamadas a setState() em lote em uma única atualização para um melhor desempenho.

Como this.props e this.state podem ser atualizadas de modo assíncrono, você não deve confiar nesses valores para o cálculo do próximo state.

U802XeMCbWXgWEqgFS1oISKMBIalMacJHIr9
Manipule o state com uma abordagem funcional

Você deve sempre fazer esse tipo de manipulação por meio de uma abordagem funcional, fornecendo o state e as props e retornando o novo state com base no anterior.

Atualizações do state são mescladas

Ao chamar setState(), o React faz a mescla (merge, em inglês) do objeto que você fornece no state atual.

No exemplo abaixo, estamos atualizando a variável dogNeedsVaccination não importando as outras variáveis de state.

A mescla é rasa. Assim, this.setState({ dogNeedsVaccination: true }) deixa as outras variáveis intactas, substituindo apenas o valor de dogNeedsVaccination.

MkFjwenYGJsPRkbOZJOcSSHaU26jJ83Y430C

Respeite o fluxo dos dados e evite maximizar o state

Os dados fluem de cima para baixo! Nem os componentes pai, nem os filhos podem saber se determinado componente tem ou não um estado. Eles também não devem ter de se importar se determinado componente foi definido como função ou como classe.

É por isso que state geralmente é chamado de local ou de encapsulado. Ele não está acessível a nenhum outro componente além daquele a que pertence e que o define.

Ao colocar uma prop em setState e usá-la em seu componente, você está quebrando o fluxo da renderização das props. Se, por alguma razão, a prop passada para seu componente for alterada no componente pai, o filho não poderá renderizá-la de novo "automagicamente"!

Vejamos um exemplo:

BdJA87j3HTnLGzQMnx0enaMdNXelqTzrkgZ1

Aqui, temos um componente Home que gera um número mágico a cada 1000ms e o define em seu próprio state.

Depois disso, ele renderiza o número e invoca três componentes Child que receberão o número mágico com o objetivo de exibi-lo usando três abordagens diferentes:

Primeira abordagem

O componente ChildOfHome respeita o fluxo em cascata das props do React e, considerando que o objetivo é apenas mostrar o número mágico, renderiza a props recebida diretamente.

DdFAz1VvRhbZ1vgJgoKYoguzuPEWRDsFkI-L

Segunda abordagem

O componente ChildOfHomeBrother recebe a props de seu pai e, invocando componentDidMount, define o número mágico no state. Em seguida, renderiza state.magicNumber.

Este exemplo não funcionará, porque render() não sabe que uma prop mudou. Assim, a mudança não dispara a nova renderização do componente. Como o componente não é renderizado novamente, componentDidMount não é invocado e a tela não é atualizada.

gPNGY21whSekaZpxB2qWutfofEw96BwgAs6X

Terceira abordagem

Geralmente, quando tentamos fazer o processo funcionar usando a segunda abordagem, achamos que falta algo. Em vez de dar um passo atrás, seguimos adicionando coisas ao código para fazê-lo funcionar!

Então, nessa terceira abordagem, adicionamos componentDidUpdate para verificar se há uma alteração na props para acionar a nova renderização do componente. Isso é desnecessário e leva a código não limpo. Essa abordagem também traz consigo custos ao desempenho que serão multiplicados pelo número de vezes que fazemos isso em uma aplicação maior, onde temos vários componentes encadeados e efeitos colaterais.

Essa é uma abordagem incorreta, a menos que você precise permitir que o usuário altere o valor da prop recebida.

Se não precisar alterar o valor da prop, sempre tente manter tudo funcionando de acordo com o fluxo do React (primeira abordagem).

Você pode conferir uma página da web funcionando com esse exemplo que eu preparei para você na Glitch. Dê uma olhada e divirta-se.

Confira também o código em Home.js e em HomeCodeCleaned.js (sem o HTML) no meu repositório a respeito deste artigo.

Como definir o state com setState

Então, chegamos ao ponto onde vamos botar a mão na massa!

Vamos brincar um pouco com o setState e melhorar o processo! Basta seguir comigo e buscar outra xícara de café!

Vamos criar um pequeno formulário para atualizar dados do usuário:

7QEN8rHC9lVLP1YX2nyTVEcC7s7G-25dDkjG
Um pequeno exercício de setState()

Aqui está o código para o exemplo acima:

YVqIxzftG-aQyjVeMvPwb5IPbpe7Xlq-C7ln
Componente Home inicial

Estamos definindo state como um objeto. Isso não é um problema, pois nosso state atual não depende do nosso último state.

E se criarmos mais um campo de formulário para introduzir e exibir o sobrenome (Last Name)?

FaqdEOSaxiOvUvHwrdTjTI4DVr9icW3-40Dt
Recurso Last Name (sobrenome)
Jut4MxFFK81esqawr6NkqHWMsn4fNVWWfbzl
handleFormChange abstraído

Boa! Abstraímos o método handleFormChange para podermos lidar com todos os campos de entrada e com o setState.

E se adicionássemos um botão de alternância para marcar os dados como válidos ou inválidos e um contador para saber quantas alterações fizemos ao state?

T52heLJjKgK8ziG6CcEuxjYDhc91qjdOxP1h
Imagem mostrando o console.log do componente state
iu0Fdle1B1iFim6GZIKf8O6z3fgjHDn4h7qe
handleFormChange atualizado com uma caixa de seleção e os handlers do contador

Isso! Está um sucesso! Abstraímos muitas coisas!

Hmmm… digamos que, agora, eu não quero uma caixa de seleção que controle a variável isValid, mas um simples botão de alternância.

Vamos também separar o handler do contador desse método. Ele funciona bem, mas em situações mais complexas onde o React precisar colocar em lote/agrupar alterações, não é uma boa política confiar na variável this.state.counter para adicionar mais uma. Esse valor pode mudar sem você perceber.

Estamos usando uma cópia rasa dele no instante em que a operação é invocada. Naquele momento específico, não sabemos se o seu valor é um que já esperávamos ou não!

Vamos partir para a abordagem funcional!

0p8DlnnaNpXtqPG81xTayly9J8VE2ibidKwK
Imagem mostrando a alternância entre Valid/Invalid e o console.log da variável state.counter
C5veVluHRXO39bXkvVKgHCWuH6japAFj3D3R
Separação dos handlers de controle

Bom, perdemos a abstração, pois separamos os handlers, mas foi por uma boa razão!

Neste momento, mantemos handleFormChange passando um objeto para o método setState da API. Mas os métodos handleCounter e handleIsValid agora são funcionais e começam buscando o state atual para, em seguida, dependendo do state, alterá-lo para o próximo.

Essa é a maneira correta de mudar o state das variáveis que dependem do state anterior.

E se quiséssemos colocar no console.log() as alterações do state dos campos de input firstName e lastName do formulário toda vez que uma alteração ocorresse? Vamos tentar!

HUCXO8J0ASy4NNfDU1zZuEtXq-zpdiIQe1ZK
Método logFields()

Boa! Toda vez que handleFormChange ocorrer (o que significa que um novo pressionamento de tecla ocorreu), o método logFields() é invocado e registrará o state atual no console!

Vamos conferir o console do navegador:

jLz33AfgQi6kldAGYf4KT479Zr2ntt3-RBUk
Imagem do console.log do state de firstName e de lastName

Opa! O que aconteceu aqui? O console.log está uma entrada de formulário antes da atual! Por que isso está acontecendo?

setState é assíncrono!

Já sabíamos disso, mas, agora, estamos vendo com nossos próprios olhos! O que está ocorrendo ali? Vamos ver os métodos handleFormChange e logFields acima.

O método handleFormChange recebe o nome do evento e o valor, depois faz umsetState desses dados. Em seguida, ele chama handleCounter para atualizar as informações do contador. Ao final, ele invoca o método logFields. O método logFields busca o currentState e retorna "Eduard" em vez de "Eduardo".

A questão é a seguinte: setState é assíncrono e não age no momento. O React está fazendo seu trabalho e executa o método logFields primeiro, deixando setState para o próximo laço de eventos.

Como podemos evitar esse tipo de situação?

Bem, a API setState tem uma função de callback para evitar essa situação:

3wyO2ef7SMFdzVrxDGpTwJkXvdEV7wCZLGh2
Método setState da API

Se quisermos que logFields() leve em consideração as alterações recentes que fizemos ao state, precisamos invocá-lo dentro da callback, assim:

GPYFMLHBhIOkbjGGdfcDm5Uz24og0hvPYtvC
Usando o handler de callback do método setState() da API

Certo, agora está funcionando!

Estamos dizendo ao React: "Olha, React! Cuidado, pois ao invocar o método logFields, eu quero que você pegue o state já atualizado, certo? Confio na sua eficiência!"

O React responde: "Pode deixar, Edu! Vou dar um jeito nesse monte de coisas que eu geralmente resolvo no quintal com o setState e somente quando eu tiver terminado é que eu vou invocar o logFields()! Tá suave! De boas!"

2YuGDOcmnseFY5lMTceR9FBGZ8h4friD-gnT
Imagem do console.log() de fullName

E, de fato, deu certo!

Bem, pessoal! Agora, já tratamos das maiores armadilhas do setState.

Prontos para ir para além da muralha? Pegue uma (outra) xícara de café e vamos nessa…

Mergulhando no setState()

Agora que temos os métodos handleCounter e handleIsValid, e que o setState() é expresso com funções, podemos compor a atualização do state com outras funções! Composição é show! Vamos nos divertir!

pBcn25XBdxdA9uqPSqRXDRbRu6fdMB5MdS-4
Abstraindo handleIsValid()

Podemos pegar a lógica de setState e colocar em uma função fora do componente de classe. Vamos chamá-lo de toggleIsValid. ☝️

MQkXig9EC7Fem4-xeipehkq1KUJweQE027QD
Função toggleIsValid

Agora, essa função pode viver fora do componente de classe e seja onde for em sua aplicação.

E se usarmos uma função de ordem superior?

AfGwofoy8ykA4pYHHr9cRiMSeUN9oEgH-yca
Mudando toggleIsValid para uma função de ordem superior

Nossa! Agora, não estamos mais invocando a função toggleIsValid. Estamos invocando uma função abstrata de ordem superior chamada toggleKey e passando uma chave (uma string, neste caso) para ela.

O que precisamos alterar na função toggleIsValid agora?

YJ1jrcCACrvShq5Je8fZYWrxSfEOdlZYWiDo
Função de ordem superior toggleKey

Quê? Agora, temos uma função chamada toggleKey que recebe uma key e retorna uma nova função que altera o state de acordo com a chave fornecida.

Essa toggleKey pode estar em uma biblioteca ou em um arquivo auxiliar. Ela pode ser invocada em vários contextos diferentes para alterar o state do que você quiser para o seu inverso.

Ótimo!

Vamos fazer o mesmo com o handler do contador de incrementos:

nkgBbIQTNdFGZfTDtZkigh1HDMCWZBnnNii9
Abstração de handleCounter para invocar uma função de ordem superior
oxRMcY3Kwj72HrWrE5IpNTDhlcb4mnaw8s44
Função de ordem superior incrementCounter()

É isso! Funciona! Que ótimo. Agora, vamos subir mais alto ainda…

Para o alto e avante... e de volta

E se criarmos uma função makeUpdater genérica, que recebe a função de transformação que queremos aplicar, pega a chave e retorna a função do state gerenciando o state com a função de transformação e com a chave? Um pouco confuso? Vamos lá!

mKBYvEDHZDt1hhK-67J-yc7G9ciZd6ONeU37
Função de ordem superior makeUpdater

Está bem. Já chega… Paramos por aqui.

Confira o código que fizemos neste repositório do GitHub.

Por fim, mas não menos importante...

Não se esqueça de evitar maximizar o uso de state e de respeitar o modelo em cascata das props de renderização em React.

Não se esqueça de que setState é assíncrono.

Não se esqueça de que setState pode receber um objeto ou uma função

Não se esqueça de que você deve passar uma função quando seu próximo state depende do state anterior.

Bibliografia

  1. Documentação do React
  2. Cursos do Reach Tech Courses, de Ryan Florence, que eu recomendo de verdade.

Muito obrigado!