Mitigando Estados com ReactJS

Mitigando Estados com ReactJS

Olá leitor, seja bem vindo a mais uma lição da incrível jornada do desenvolvedor ReactJS 😄

Nesta lição vamos aprender um pouco mais a fundo sobre o funcionamento dos estados em uma aplicação feita com ReactJS.

O que esperar desta lição?

Um dos maiores desafios de todo desenvolvedor ReactJS (eu me incluo nessa) é ter um entendimento aprofundado sobre o funcionamento do processo de re-renderização de uma aplicação feita com ReactJS.

A maioria dos desenvolvedores que vemos por aí, começam a criar aplicações em ReactJS sem ao menos entender a fundo como elas funcionam, tais desenvolvedores entendem o suficiente para sobreviver no mercado de trabalho e ganhar seu pão de cada dia.

Nesta lição eu busco te oferecer um passo a passo do que você precisa fazer para trabalhar com estados da melhor forma, além de outros assuntos relevantes é claro 🤓

Entendendo o Loop principal de uma aplicação em ReactJS

Como você já sabe, um estado em ReactJS é uma estrutura de dados que representa informações mutáveis e dinâmicas dentro de um componente.

Um estado é usado para armazenar dados que podem ser modificados ao longo do tempo durante a execução de um aplicativo, tudo isso sincronizado em tempo real com a interface do usuário.

Legal, sendo assim, podemos dizer que: Toda nova renderização que acontece no ReactJS sempre começa com uma mudança de estado!

E nós já vimos como isso acontece em lições anteriores, não é verdade?

Nesse caso, uma mudança de estado é o único "gatilho" que a aplicação tem para renderizar novamente um componente na tela.

O único problema é que, quando um componente é renderizado novamente, automaticamente a nossa aplicação renderiza também todos os componentes que estão ligados a ele.

Vamos ver um exemplo.

Contador > index.jsx:

import React, {useState} from 'react';
import Cabecalho from '../Cabecalho';

const Contador = () => {
 const [contador, setContador] = useState(0);

 return (
 <>
 <Cabecalho />
 <h2>Contador: {contador}</h2>
 <br />
 <button onClick={() => setContador(contador + 1)}>Incrementar</button>
 </>
 );
}

export default Contador;

Cabecalho > index.jsx:

import React from 'react';

const Cabecalho = () => {

 const retornarAno = () => {
 console.log('O ano atual está sendo retornado...');
 return 2024;
 }

 return (
 <header>
 <p>Olá usuário, seja bem vindo!</p>
 <small>{retornarAno()}</small>
 </header>
 );
}

export default Cabecalho;

No código acima criamos o componente Contador que faz o uso do useState para controlar de forma dinâmica quantas vezes o usuário clicou no botão "incrementar".

O único problema (já previamente identificado por nós em lições anteriores) é o fato do componente Cabecalho estar re-renderizando toda vez que o usuário clica no botão "incrementar".

Isso só aconteceu, pois cada variável de estado é anexada a uma instância específica de um componente.

Portanto: Todo o componente (pai, filhos e correlacionados) serão renderizados novamente sempre quando uma variável de estado existente dentro de um componente pai tiver seu valor alterado.

Isso significa que as novas renderizações que vão acontecendo, apenas afetam o componente que possui aquele estado em conjunto com seus descendentes (caso houver).

Já o componente App.js, na qual estamos usando para importar o componente Contador, não é renderizado quando o estado do Contador muda:

import Contador from "./components/Contador";

export default function App() {
 return (
 <div>
 <Contador />
 </div>
 );
}

O App.js não foi renderizado pois ele está em um nível acima do componente Contador, logo, o ReactJS não faz essa re-montagem.

Agora... se tivéssemos um controle de estado dentro do App.js, você pode ter certeza que a cada alteração ocorrida, levaria a re-renderizações de todos os componentes ali importados, incluindo todos os seus descendentes (Contador, Cabecalho e afins).

Mas por que isso acontece dessa forma no ReactJS?

Para entender tudo isso, precisamos cavar um pouco mais a fundo!

Como o ReactJS sincroniza a UI?

Antes de mais nada, a principal tarefa de um estado é manter a UI (Interface do Usuário) do aplicativo sincronizada (em tempo real) com os valores que aquele estado armazena.

É como um contador de passos que você usa no seu aplicativo de celular para contabilizar quantos passos você dá por dia.

Ou um oximetro que contabiliza em tempo real a quantidade de batimentos cardiacos, e a taxa de oxigênio que o seu corpo produz em tempo real.

Ou ainda podemos comparar a quantidade de RPM (Rotações por minuto) que uma bicicleta egométrica contabiliza a medida em que você pedala.

O que todos esses exemplos acima tem em comum?

Todos eles escutam em tempo real o que está acontecendo em locais onde a gente não consegue ver, de forma a mostrar-nos o que realmente está acontecendo ali.

No âmbito de uma aplicação em ReactJS, o objetivo de uma nova renderização é descobrir o que precisa mudar.

Vamos pegar como exemplo o nosso componente Contador, que por de baixo dos panos, quando o aplicativo é montado pela primeira vez, ou seja, quando ele aparece na tela do usuário, o ReactJS renderiza todos os nossos componentes de forma a apresentar o seguinte esboço a DOM do navegador:

<div id="root">
	<div>
		<header>
			<p>Olá usuário, seja bem vindo!</p>
			<small>2024</small>
		</header>
		<h2>Contador: 0</h2>
		<br>
		<button>Incrementar</button>
	</div>
</div>

No código acima vemos um código HTML bem simples, que representa a estrutura que está sendo carregada no DOM do seu navegador.

Olhando apenas o código HTML, podemos perceber que em nenhum momento o botão "incrementar" está chamando alguma função do Javascript.

Isso significa que devem existir observadores ligados a comandos Javascript, que ficam observando quando o usuário clica no botão de modo a chamar uma função.

Já quando o usuário clica no botão "incrementar", é bem provável que uma função do Javascript seja chamada, de modo a atualizar a variável de estado (contador e setContador) mudando seu valor de 0 para 1.

E é aí que está o problema!

Em vez do ReactJS aproveitar aquela mesma estrutura HTML que já está carregada na DOM, afim de executar um comando apenas para atualizar o conteúdo existente dentro do elemento <h2>, ELE NÃO FAZ ISSO!

Pois o ReactJS, ele prefere re-gerar uma nova estrutura HTML com os dados atualizados 😡

<div id="root">
	<div>
		<header>
			<p>Olá usuário, seja bem vindo!</p>
			<small>2024</small>
		</header>
		<h2>Contador: 1</h2>
		<br>
		<button>Incrementar</button>
	</div>
</div>

De modo que toda vez que você clica no botão "incrementar", uma nova estrutura HTML como a vista acima será gerada, mudando apenas o número associado ao contador.

Antes de chamar o ReactJS de "burro" ou "imbecil", vamos entender só mais uma coisa 😆

Você precisa entender que esse processo de re-renderização acontece de forma instantânea, como se fosse uma foto tirada por uma câmera que mostra como deve ser a interface do usuário, com base no estado atual da sua aplicação.

Por de baixo dos panos, o ReactJS está jogando um jogo chamado de "Encontre as diferenças" ou o "Jogo dos 7 erros", buscando descobrir o que mudou entre essas duas fotografias.

Na animação acima, ele compara o estado anterior com o estado atual de modo a identificar o que mudou, e quando ele percebe que houve uma diferença (por menor que ela seja), ele atualiza novamente o DOM com a versão mais atualizada do HTML.

Maaaas, isso não exclui o fato do ReactJS re-carregar desnecessáriamente um componente, pois primeiro ele recarrega todos os componentes de modo a gerar a estrutura HTML final, para só depoooois fazer a comparação.

Feito isso, o ReactJS se acomoda e ficar aguardando alterações nos estados para re-fazer todo esse processo novamente.

E esse é o funcionamento do Loop de Renderização de uma aplicação em ReactJS!

O que vem abaixo disso, é a lógica de como tudo isso funciona 😉

Creio que a partir de agora você vai olhar a animação abaixo com outros olhos:

Note que o DOM carregado pelo seu navegador, ele fica piscando toda hora em que clicamos no "Botão Incrementar":

E isso significa que após a comparação ("jogo dos 7 erros"), o ReactJS só altera o nó (elemento HTML específico) que sofreu alteração.

Sendo assim, o objetivo de uma nova renderização, é descobrir como uma mudança de estado deve afetar a interface do usuário.

E, para isso, o ReactJS precisa renderizar novamente todos os componentes potencialmente afetados para obter uma fotografia precisa, de modo a fazer a sua comparação.

Só que tudo isso acontece de uma forma bem rápida e imperceptível.

Props também geram re-renderizações

Em lições anteriores, você também viu que componentes que recebem props sofrem re-renderizações "desnecessárias".

Usei aspas no termo desnecessárias, pois nós acabamos de ver que elas não são tão desnecessárias assim, uma vez que se tratam da forma como o ReactJS opera, ao mesmo tempo que não podemos fazer muita coisa para mudar isso.

Dessa vez, vamos seguir o mesmo exemplo do componente Contador, a diferença é que iremos passar a contagem para um novo componente chamado de Amostrador, e além disso,  vamos inserir um outro componente que representa uma imagem (que no caso será a logo da Micilini) chamado de LogoMicilini.

Contador > index.jsx:

import React, {useState} from 'react';
import Cabecalho from '../Cabecalho';
import Amostrador from '../Amostrador';
import LogoMicilini from '../LogoMicilini';

const Contador = () => {
 const [contador, setContador] = useState(0);

 return (
 <>
 <Cabecalho />
 <Amostrador contador={contador} />
 <br />
 <button onClick={() => setContador(contador + 1)}>Incrementar</button>
 <br />
 <LogoMicilini />
 </>
 );
}

export default Contador;

Amostrador > index.jsx:

import React from 'react';

const Amostrador = (props) => {
 return (
 <>
 <h2>Amostrador: {props.contador}</h2>
 </>
 );
}

export default Amostrador;

LogoMicilini > index.jsx:

import logo from '../../assets/logo_micilini_github.svg';

const LogoMicilini = () => {
 return (
 <>
 <br />
 <img src={logo} alt="Logo Micilini" width="120" />
 </>
 );
}

export default LogoMicilini;

Observação: O componente Cabecalho permaneceu intacto!

Como podemos ver no código acima, temos uma novo logo no final da página, e que não depende da contagem, o que indica que não será renderizado novamente quando clicar no botão "incrementar", certo?

ERRADO!

Quando um componente é renderizado novamente, o ReactJS tenta renderizar todos os seus descendentes, não importando muito se uma variável de estado que está sendo passada, é tratada em outro componente por meio de props ou não (como está acontecendo no Amostrador).

Isso acontece pois o ReactJS tem dificuldades em saber (com 100% de certeza) se o componente <LogoMicilini> depende de forma direta ou indireta do componente Contador.

Então pelo sim ou pelo não, o ReactJS acha melhor re-renderizar esse componente novamente.

Em um mundo ideal, todos os nossos componentes seriam imutáveis, ou seja, componentes que sempre produzem o mesmo código HTML (sem alterações) mesmo quando recebem as mesmas propriedades.

Infelizmente no mundo real, o ReactJS não age dessa forma, onde ele considera todos os componentes como mutáveis, mesmo que nós desenvolvedores falamos o contrário.

Para criar um componente mutável é relativamente simples, observe esse componente que sempre pega a data e hora atual do sistema:

const DataAtual = () => {
 const hoje = new Date();
 return (
 <p>Hoje é {hoje.toString()}</p>
 );
}

Agora veja esse outro componente que representa um componente imutável:

const DiaDoDesenvolvedor = () => {
 return(
 <p>12 de setembro</p>
 )
}

A verdade é que o ReactJS não se preocupa muito se o componente mudou ou não, o que importa para ele é re-renderizar tudo de novo e verificar o que mudou ou não.

No componente Amostrador por exemplo, nós poderíamos passar sempre o mesmo número quando o usuário clicar no botão "incrementar":

<Amostrador contador={1} />

Apesar de sabermos que o conteúdo daquele componente NUNCA VAI MUDAR (pois o valor passado para ele sempre será 1), o ReactJS não é capaz de dizer se modificamos alguma coisa ou não, então pelo sim ou pelo não, ele re-renderiza aquele componente por segurança.

Como o objetivo do ReactJS é garantir que a UI do usuário esteja sincronizada em tempo real com o que acontece por de baixo dos panos na sua aplicação, ele não quer correr o risco de mostrar ao usuário uma interface de usuário obsoleta e desatualizada.

E é por isso que ele prefere re-renderizar os componentes!

Apesar desse ser o modo de operação de uma aplicação em ReactJS, existem algumas maneiras de contornar isso de modo a melhorar a performance da nossa aplicação.

Criando componentes imutáveis

Na lição que fala sobre performance das nossas aplicações em ReactJS, você aprendeu a fazer o uso do React.memo, que nada mais é do que uma função de ordem superior na qual podemos memoriza a renderização de componentes.

O que os tornam componentes imutáveis, ou seja, componentes que não possuem (ou que não costumam possuir) seus estados e propriedades modificados.

Ainda trabalhando em cima do componente Contador, se pegarmos o componente Cabecalho e também o LogoMicilini, poderiamos aplicar o React.memo de modo a torná-los imutáveis:

Cabecalho > index.jsx:

import React from 'react';

const Cabecalho = () => {

 const retornarAno = () => {
 console.log('O ano atual está sendo retornado...');
 return 2024;
 }

 return (
 <header>
 <p>Olá usuário, seja bem vindo!</p>
 <small>{retornarAno()}</small>
 </header>
 );
}

export default React.memo(Cabecalho);

LogoMicilini > index.jsx:

import logo from '../../assets/logo_micilini_github.svg';
import React from 'react';

const LogoMicilini = () => {
 return (
 <>
 <br />
 <img src={logo} alt="Logo Micilini" width="120" />
 </>
 );
}

export default React.memo(LogoMicilini);

Com isso, fariamos com que o ReactJS se lembrasse da fotografia anterior de modo a reutilizá-la na nova comparação evitando re-renderizações desnecessárias, isto é, caso o seu componente não sofresse qualquer tipo de alteração.

É como se o seu componente falasse assim: "Se liga, eu sou um componente imutável e você não precisa me renderizar novamente, quando algo aqui mudar, eu te aviso e você me renderiza de novo, beleza? Mas só faça isso quando eu sofrer alterações".

Pense no uso do React.memo como um fotógrafo preguiçoso, se você pedir para ele tirar 12 fotos exatamente da mesma coisa, ele tirará apenas uma única foto e lhe dará 11 cópias da mesma. E tenho certeza de que ele só vai tirar uma nova foto quando suas instruções mudarem.

Atualmente os criadores e mantenedores do ReactJS estão testando novas formas de aposentar o React.memo, e tornar o framework bastante inteligente para ser capaz de identificar por conta própria, se um componente deve ou não ser re-renderizado na tela.

Para saber mais acompanhe este vídeo do time de desenvolvimento do ReactJS que fala sobre React without memo.

O uso de estados aplicado ao conceito de SOLID

Se você é um desenvolvedor que busca entender as melhores práticas de desenvolvimento, com certeza já ouviu falar sobre SOLID.

O SOLID é um acrônimo que representa um conjunto de cinco princípios de design de software orientado a objetos que visam facilitar o desenvolvimento, manutenção e escalabilidade de sistemas de software.

O simples fato de estarmos falando sobre abstração de responsabilidades usando service, por si só, faz parte de um dos príncipios de SOLID 😄

E sim, é totalmente possível usar estados em conjunto com o SOLID, mas você tem que tomar alguns cuidados para não cair em pegadinhas!

E pensando nelas, eu resolvi trazer dois grandes exemplos para você 🙂

Abstraindo um componente de retorno de nome usando um service

A ideia principal deste tópico é que tenhamos um componente chamado de Nome, cujo o objetivo é alterar o nome do usuário que aparece na tela, assim que clicarmos no botão "Mudar Nome".

Só que em vez de criar essa lógica dentro do próprio componente, nós iremos abstrair uma parte dela para dentro de um service chamado de nomeService.js.

Vamos ver como tudo isso funciona!

Nome > index.jsx:

import React, { useState, useEffect } from 'react';
import NomeService from '../../services/nomeService';

const Nome = () => {
 const nameService = new NomeService();
 const [name, setName] = useState('');

 useEffect(() => {
 loadName();
 }, []);

 const loadName = () => {
 setName(nameService.getName());
 }

 const changeName = () => {
 nameService.changeName();
 loadName();
 }

 return(
 <div>
 <p>{name}</p>
 <button onClick={changeName}>Muda Nome</button>
 </div>
 )
}

export default Nome;

nomeService.js:

class NomeService {
 constructor() {
 this.name = '...';
 }

 getName(){
 return [...this.name];
 }

 changeName(){
 this.name = 'Micilini Roll';
 }
}

export default NomeService;

Veja como ficou o resultado final:

Apesar desse código funcionar, ele tem um pequeno problema!

Para exemplificá-lo, vamos criar mais um estado dentro de Nome onde poderemos setar a idade desse usuário:

Nome >index.jsx:  

import React, { useState, useEffect } from 'react';
import NomeService from '../../services/nomeService';

const Nome = () => {
 const nameService = new NomeService();
 const [name, setName] = useState('');

 const [age, setAge] = useState(0);

 useEffect(() => {
 loadName();
 }, []);

 const loadName = () => {
 setName(nameService.getName());
 }

 const changeName = () => {
 nameService.changeName();
 loadName();
 }

 const changeAge = () => {
 const random = Math.floor(Math.random() * (109 - 1) ) + 1;
 setAge(random);
 }

 return(
 <div>
 <p>{name} / {age}</p>
 <button onClick={changeName}>Muda Nome</button>
 <button onClick={changeAge}>Muda Idade</button>
 </div>
 )
}

export default Nome;

Perceba que propositalmente nos não abstraímos a idade desse usuário para o nosso service.

Como estamos vendo na gravação acima, esse código funciona perfeitamente!

Mas será mesmo que ele funciona tão perfeitamente assim?

Experimente executar um console.log dentro do constructor do nomeService.js:

class NomeService {
 constructor() {
 console.log('nomeService sendo chamado...');
 this.name = '...';
 }

 getName(){
 return [...this.name];
 }

 changeName(){
 this.name = 'Micilini Roll';
 }
}

export default NomeService;

Ao clicar em um dos botões ("Mudar Nome" ou "Mudar Idade") a mensagem "nomeService sendo chamado..." aparecerá diversas vezes.

Isso aconteceu pois cada vez que o ReactJS renderiza um componente, a função é executada incluindo todo o código nele contido.

Portanto, quando chamamos o setName dentro de loadName, uma nova instancia de nomeService será chamada.

E apesar do nome continuar armazenando o mesmo texto (Micilini Roll), por de baixo dos panos ele armazena primeiro o texto padrão (...) e depois altera automaticamente para Micilini Roll.

É importante ressaltar que esse é o comportamento normal da classe, e não está errado, ok?

Nesse exemplo em específico, para o usuário final está tudo funcionando perfeitamente, uma vez que ele continua visualizando o nome alterado, mas na visão do desenvolvedor tem algo de errado.

Agora, vamos passar a lógica do estado idade (age) para dentro do nomeService.js:

Nome > index.jsx:

import React, { useState, useEffect } from 'react';
import NomeService from '../../services/nomeService';

const Nome = () => {
 const nameService = new NomeService();
 const [name, setName] = useState('');
 const [age, setAge] = useState(0);

 useEffect(() => {
 loadName();
 loadAge();
 }, []);

 const loadName = () => {
 setName(nameService.getName());
 }

 const loadAge = () => {
 setAge(nameService.getAge());
 }

 const changeName = () => {
 nameService.changeName();
 loadName();
 }

 const changeAge = () => {
 nameService.changeAge();
 loadAge();
 }

 return(
 <div>
 <p>{name} / {age}</p>
 <button onClick={changeName}>Muda Nome</button>
 <button onClick={changeAge}>Muda Idade</button>
 </div>
 )
}

export default Nome;

nomeService.js:

class NomeService {
 constructor() {
 console.log('nomeService sendo chamado...');
 this.name = '...';
 this.age = 0;
 }

 getName(){
 return this.name;
 }

 changeName(){
 this.name = 'Micilini Roll';
 }

 getAge(){
 return this.age;
 }

 changeAge(){
 const random = Math.floor(Math.random() * (109 - 1) ) + 1;
 this.age = random;
 }
}

export default NomeService;

Como vemos na gravação acima, não modou nada, não é verdade? Pois o nosso código continua funcionando normalmente.

Agora vamos modificar o nosso código de modo a criar um objeto dentro de NomeService.

nomeService.js:

class NomeService {
 constructor() {
 console.log('nomeService sendo chamado...');
 this.data = {
 name: '...',
 age: 0
 }
 }

 getData(){
 return this.data;
 }

 changeName(){
 this.data.name = 'Micilini Roll';
 }

 changeAge(){
 const random = Math.floor(Math.random() * (109 - 1) ) + 1;
 this.data.age = random;
 }
}

export default NomeService;

Nome > index.jsx:

import React, { useState, useEffect } from 'react';
import NomeService from '../../services/nomeService';

const Nome = () => {
 const nameService = new NomeService();
 const [name, setName] = useState('');
 const [age, setAge] = useState(0);

 useEffect(() => {
 loadName();
 loadAge();
 }, []);

 const loadName = () => {
 const data = nameService.getData();
 setName(data.name);
 }

 const loadAge = () => {
 const data = nameService.getData();
 setAge(data.age);
 }

 const changeName = () => {
 nameService.changeName();
 loadName();
 }

 const changeAge = () => {
 nameService.changeAge();
 loadAge();
 }

 return(
 <div>
 <p>{name} / {age}</p>
 <button onClick={changeName}>Muda Nome</button>
 <button onClick={changeAge}>Muda Idade</button>
 </div>
 )
}

export default Nome;

Até aí tudo bem, rodando o código, conseguimos perceber que a mensagem "nomeService sendo chamado..." não aparece mais quando clicamos no botão "Muda Nome" mais de uma vez, somente no "Muda Idade".

O que representa uma pequena correção para o nosso problema.

Mas antes de te dar a correção final, quero que você preste bastante atenção no tópico a seguir.

Abstraindo um componente de criação de itens usando services

Agora nós iremos criar um componente bem simples de gerenciamento de itens, chamado de ListaTarefasSimples.

ListaTarefasSimples > index.jsx:

import React, { useState, useEffect } from 'react';

const ListaTarefasSimples = () => {
 const [list, setList] = useState([
 { title: 'Item 1' },
 { title: 'Item 2' },
 { title: 'Item 3' }
 ]);

 const addNewItem = () => {
 const newList = [...list, { title: `Item ${list.length + 1}` }];
 setList(newList);
 }

 return(
 <div>
 <h2>Lista de Itens:</h2>
 {list.map((item, index) => (
 <div key={index}>
 <h3>{item.title}</h3>
 </div>
 ))}
 <button onClick={addNewItem}>Adicionar Novo Item</button>
 </div>
 )
}

export default ListaTarefasSimples;

Veja o resultado final:

Supondo que queremos abstrair essa lista de itens para dentro de um service, nós podemos criar e chamá-lo de itensService.js:

itensService.js:

class ItensService {
 constructor() {
 this.list = [
 { title: 'Item 1' },
 { title: 'Item 2' },
 { title: 'Item 3' }
 ];
 }

 getList(){
 return [...this.list];
 }

 addNewItem(){
 const newList = [...this.list, { title: `Item ${this.list.length + 1}` }];
 this.list = newList;
 }
}

export default ItensService;

Vamos criar também um outro componente chamado de ListaTarefasAvancado:

ListaTarefasAvancado > index.jsx:

import React, { useState, useEffect } from 'react';
import ItensService from '../../services/itensService';

const ListaTarefasAvancado = () => {
 const [list, setList] = useState([]);
 const listService = new ItensService();

 useEffect(() => {
 loadList();
 }, []);

 const loadList = () => {
 setList(listService.getList());
 }

 const addNewItem = () => {
 listService.addNewItem();
 loadList();
 }

 return(
 <div>
 <h2>Lista de Itens:</h2>
 {list.map((item, index) => (
 <div key={index}>
 <h3>{item.title}</h3>
 </div>
 ))}
 <button onClick={addNewItem}>Adicionar Novo Item</button>
 </div>
 )
}

export default ListaTarefasAvancado;

Veja como ficou o resultado final:

Estranho não? Pois apesar da nossa lógica fazer sentido, por qual motivo os novos itens não estão sendo adicionados a lista?

Primeiro, o item está sendo adicionado a lista sim! A diferença é que ele está sendo adicionado sempre na mesma posição, em vez de novas, o que representa uma sobrescrição.

Novamente, nos deparamos com omesmo problema do componente que criamos no tópico anterior, onde a cada vez que o ReactJS renderiza um componente ou muda o determinado valor de um estado, uma função é executa de modo a incluir todo código ali contido.

Sendo assim, quando nós chamamos o setList, um novo ItensService será instanciado, uma vez que o setList atualiza todos os estados que acionam uma nova renderização do componente.

Para resolver isso nós podemos instanciar nosso service por meio de um useMemo da seguite forma:

const listService = useMemo(() => new ListService()

Ou caso preferir, você também pode carregar a lista diretamente no useEffect, de modo a gerenciar o estado do seu componente a partir do primeiro carregamento do mesmo:

const [list, setList] = useState([]);

useEffect(() => {
 setList(new ItensService().getList());
}, []);

E como ItensService.getList() não é assíncrono, também podemos assim:

const [list, setList] = useState(new ListService.getList())

ListaTarefasAvancadas > index.jsx:

import React, { useState, useEffect } from 'react';
import ItensService from '../../services/itensService';

const itensService = new ItensService();

const ListaTarefasAvancado = () => {
 const [list, setList] = useState(itensService.getList());

 useEffect(() => {
 loadList();
 }, []);

 const loadList = () => {
 setList(itensService.getList());
 }

 const addNewItem = () => {
 itensService.addNewItem();
 loadList();
 }

 return(
 <div>
 <h2>List of Items:</h2>
 {list.map((item, index) => (
 <div key={index}>
 <h3>{item.title}</h3>
 </div>
 ))}
 <button onClick={addNewItem}>Add new Item</button>
 </div>
 )
}

export default ListaTarefasAvancado;

itensService.js:

class ItensService {
 constructor() {
 this.list = [
 { title: 'Item 1' },
 { title: 'Item 2' },
 { title: 'Item 3' }
 ];
 }
 
 getList(){
 return [...this.list];
 }
 
 addNewItem(){
 const newList = [...this.list, { title: `Item ${this.list.length + 1}` }];
 this.list = newList;
 }
}
 
export default ItensService;

Veja como ficou o resultado final:

Incrível não?

Temos que estar atentos a essas possíveis pegadinhas que os estados do ReactJS podem nos dar.

Então se você está usando services para abstrair parte da lógica dos seus componentes, cuidado, pois eles podem não funcionar da maneira desejada.

Com as dicas citadas acima, creio que você já saiba o que fazer caso um dia se deparar com esses problemas 🙂

Corrigindo o componente Nome

Agora que já sabemos o que fazer para corrigir o problema de re-renderizações desnecessárias no componente Nome, basta que instanciemos nosso service fora do componente.

Nome > index.jsx:

import React, { useState, useEffect } from 'react';
import NomeService from '../../services/nomeService';

const nameService = new NomeService();

const Nome = () => {
 const [name, setName] = useState(nameService.getData().name);
 const [age, setAge] = useState(nameService.getData().age);

 useEffect(() => {
 loadName();
 loadAge();
 }, []);

 const loadName = () => {
 const data = nameService.getData();
 setName(data.name);
 }

 const loadAge = () => {
 const data = nameService.getData();
 setAge(data.age);
 }

 const changeName = () => {
 nameService.changeName();
 loadName();
 }

 const changeAge = () => {
 nameService.changeAge();
 loadAge();
 }

 return(
 <div>
 <p>{name} / {age}</p>
 <button onClick={changeName}>Muda Nome</button>
 <button onClick={changeAge}>Muda Idade</button>
 </div>
 )
}

export default Nome;

E veja só... a mensagem "nomeService sendo chamado..." simplismente parou de aparecer 🤩

A constante nameService foi declarada fora do componente principal para que ela seja criada apenas uma única vez durante toda a vida útil do aplicativo.

Quando você cria uma instância de NomeService fora do componente, ela não será recriada toda vez que o componente for renderizado. Em vez disso, a mesma instância será utilizada em todos os casos.

Essa abordagem se torna bem útil se você quiser compartilhar a mesma instância de serviço entre vários componentes, ou se quiser manter o estado do serviço ao longo do tempo.

Se nameService fosse declarada dentro do componente Nome, uma nova instância seria criada toda vez que o componente fosse renderizado.

Isso poderia resultar em sobrecarga desnecessária, especialmente se NomeService contiver alguma lógica de inicialização pesada, ou se precisar manter o estado entre renderizações do componente.

Além disso, se você quisesse compartilhar o mesmo serviço entre vários componentes, teria que passá-lo como propriedade ou usar um gerenciamento de estado global.

Portanto, ao declarar nameService fora do componente, você está garantindo uma abordagem mais eficiente e compartilhável para gerenciar o serviço de nome🙂

Arquivos da lição

Os arquivos dessa lição podem ser encontrados neste repositório do GitHub.

Conclusão

Nesta lição mitigamos os conceitos principais do uso de estados em uma aplicação em ReactJS.

Te aguardo na próxima lição 😇