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
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.
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
.
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:
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.
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.
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:
Aqui está o código para o exemplo acima:
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)?
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?
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!
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!
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:
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:
Se quisermos que logFields()
leve em consideração as alterações recentes que fizemos ao state, precisamos invocá-lo dentro da callback, assim:
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!"
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!
Podemos pegar a lógica de setState
e colocar em uma função fora do componente de classe. Vamos chamá-lo de 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?
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?
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:
É 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á!
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
- Documentação do React
- Cursos do Reach Tech Courses, de Ryan Florence, que eu recomendo de verdade.
Muito obrigado!