useState — Parte 2 — Aprofundando em React Hooks (2021)

Ricardo Pedroni
RPEDRONI
Published in
7 min readFeb 11, 2021

Bem-vindos à parte 2 da nossa aula sobre nosso grande hook, useState! Caso perdeu a parte 1, pula aqui e veja nossa explicação dos conceitos básicos e não tão básicos desse g̵a̵n̵c̵h̵o̵ hook querido.

Na aula de hoje, vamos ver como o React trata do ciclo de render quando usamos useState , que garantias ele te dá quanto a identidade da função de atualização de estado e como podemos otimizar a inicialização de estado com lazy initialization para otimizar funções custosas. Bora lá?

Veja essa aula também no nosso canal do YouTube! 🎥

Renderiza Para Mim

Na parte 1, falamos sobre o useState servir para manter e atualizar estado entre as chamadas dos componentes (lembrando que não são nada mais do que funções) mas também de servir para notificar ao React que queremos que ele “pinte” a tela, conhecido no mundo frontend (FE) de fazer um render.

Ilustrado na imagem, o React versão 0.0.1-non-LTS-closed-beta

Chame o render de renderizar, pintar, atualizar ou redesenhar a tela, fica a seu critério. A única coisa importante que você precisa saber é que uma das coisas mais custosas que uma aplicação FE faz é isso — pintar a tela é caro. Se fazer uma soma a + bdemora 1 unidade de tempo T, para um computador, renderizar coisas gráficas isso pode facilmente ser milhares, milhões ou bilhões de Ts, dependendo de quanto trabalho é necessário para fazer essa atualização.

É por isso que boa parte da responsabilidade de desenvolvedores de FE é minimizar o número de vezes que vamos rerenderizar algo na tela, justamente para não afetarmos o desempenho percebido das nossas aplicações — via de regra, queremos sempre manter no mínimo 60 quadros por segundo, ou fps, se possível.

⚠️ Atenção: por mais que estamos discutindo otimização aqui, lembre-se que otimizar uma aplicação é um dos últimos passos da sua construção — trazer valor de negócio vale muito mais do que salvar alguns ciclos de clock, ok? Ok, voltando à nossa programação.

Tendo isso em mente, o React foi construído para ajudar muito nessa tarefa. Internamente, o React tem alguns algoritmos inteligentes para minimizar o número de vezes que um render precisa ser feito. No caso do useState, quando atualizamos um valor usando a função de atualização, tipicamente vamos forçar o React a fazer um rerender, para que possamos mostrar o novo valor no componente. Faça o tests com um console.log para ver isso em ação

Aperta o botão algumas vezes e seu console amigavelmente apontará:

Renderizô memo

E isso faz sentido, porque como o valor do nosso exemplo, o count foi atualizado, o React precisa rerenderizar para que a tela mostre o valor correto para o usuário.

A maioria das pessoas sabem que o useState força um rerender mas o que muitos não sabem é que o React controla isso para que o render só precise ser feito se um valor novo vai aparecer na tela — ou seja, se um valor de estado não mudar, ele não força um outro render desnecessário. Vamos mudar nosso componente um pouco, criando mais um botão:

Clicando no botão + , vai disparar vários renders, o que faz sentido. Mas no caso do botão Set to 5 , experimente clicar algumas vezes para ver o que acontece:

Apertei 300 vezes, pra garantir

Repare que o React faz apenas 1 render (na verdade, 2, mas esqueça disso — ou se estiver curioso, pergunte nos comentários que te respondo :) e nunca mais renderiza o componente enquanto o valor de estado não for um novo valor de estado. Bacana, né?

Mas Não Renderiza Tanto Assim

Essa forma “inteligente” que o React faz um render ou não não é tããããão inteligente assim na prática. Por baixo do panos, quando um setState é chamado, o React faz uma comparação tripla === entre o valor que ele tem salvo e o valor “novo” que está recebendo. Se os dois valores forem iguais, ou seja, se currentValue === newValue, o React não fará um novo render e isso é bom 👏

Dito isso, uma fonte de c̵a̵g̵a̵d̵a̵ problemas que pode aparecer quando trabalhamos com o useState é quando estamos salvando o valor de objetos inteiros, como no exemplo:

Estamos usando o useState para guardar uma instância de user e temos um botão que reseta esse usuário, colocando o username dele de volta ao valor original (rpedroni nesse caso). Mesmo sem nunca mudar o valor do username , experimenta clicar no botão umas 10 vezes e veja o que acontece.

35 vezes, pra garantir mesmo

Se o valor é o mesmo sempre, por que que o React está forçando um novo render?

A resposta é porque a “inteligência” dele é limitada. Como a comparação === é feita para ver se o suposto novo valor é novo mesmo e como o JavaScript, quando compara dois objetos distintos (lembra que const a = {}; const b = {}; console.log(a === b);) vai retornar false já que esses objetos, por mais que sejam iguais de conteúdo, apontam para coisas diferentes. Consequentemente, um novo ciclo de renderização acontece toda vez, independente se o que está na tela é igual.

Indo além no limites do React para impedir c̵a̵g̵a̵d̵a̵s̵ renders desnecessários, se houve um useState onde nem-estamos-usando-o-fucking-valor, o React vai rerenderizar o componente, desde que a comparação prevValue !== newValue seja verdade

…e todos os Renderizô! vão aparecer lá.

Portanto se atente quando estiver trabalhando com objetos mais complexos com o useState . Já discutimos que não é a melhor ferramenta para isso, sendo um useReducer da vida melhor. Uma outra forma de fazer o React não rerenderizar o componente em casos assim é usar o React.memo (serve para props, mas funciona similarmente), que é um HOC que controla o ciclo de renderização onde você pode impedir se o componente rerenderiza ou não, parecido com o shouldComponentUpdate() dos componentes de classe.

Me Otimize de Jeito

O useState possui um modo de inicialização interessante para quando o valor inicial do estado é resultado de uma operação custosa, ou seja, de algo que pode pesar no desempenho do front.

Primeiramente, peço desculpas se travei seu computador ou browser com esse código 😬 Segundamente, imagine que getValue seja algo útil, calculando algum valor necessário para setar o valor inicial do count, de uma forma custosa que não podemos otimizar.

Você pode imaginar que o primeiro render desse componente vai ser lerdo porque precisamos fazer o cálculo do valor e salvar no count. Mas clicando no botão alguma vezes para atualizar o count, por mais que isso não precisa do valor oriundo do getValue, força um rerender bastante lerdo. E aí, tem alguma qual a razão disso?

É sempre bom lembrar que componentes funcionais nada mais são do que funções que são chamadas em todo render. Usando o getValue dentro do MyComponent, toda vez que o componente renderiza, MESMO que não precisamos do valor retornado do getValue, o getValue vai ser chamado.

Uma solução para isso é jogar o getValue para fora do componente e isso funcionaria, mas pense nua função como getValue(someProp), que recebe uma prop do componente, então nesse caso precisaria necessariamente estar dentro do MyComponent

Para esses casos, o useState fornece uma inicialização por função chamada lazy initialization (inicialização preguiçosa, isso mesmo). Recebendo uma função, o useState entende que deve chamar a função no primeiro render e nunca mais depois disso

Um única vez.

Identidade Única

Uma última coisa antes de acabar a aula e liberar vocês para o recreio — uma coisa importantíssima que o React garante é que a função de atualização devolvida pelo useState (a setXXX) tem identidade estável. Isso significa que essa função será sempre a mesma entre um render e outro. Verifica isso aí no código abaixo:

Clica no botão para rerenderizar o component algumas vezes. E, como mágica:

⚠️ Caso retornar false para vocês, experimenta remover o <React.StrictMode> do app e veja se continua. Se quiser saber a razão disso acontecer, manda a pergunta nos comentários abaixo!

Você pode perguntar então “Professor Ricardo, por que isso é importante?”. Excelente pergunta! Mas vamos responder isso na aula que vem, quando veremos nosso próximo g̵a̵n̵c̵h̵o̵ hook amigo, o useEffect. Te vejo lá!

Espero que esse post tenha ajudado!
Ficou com dúvida ou quer mandar uma real? Deixa nos comentários!
Ah, e me acompanhe também no YouTube: https://bit.ly/3q0TIAU ✌!

Aqui é o Professor Ricardo saindo, fique na paz e até a próxima!

--

--

Ricardo Pedroni
RPEDRONI

O Professor Ricardo Pedroni ensina conceitos importantes e boas práticas de desenvolvimento de projetos em software. YouTube https://bit.ly/3q0TIAU