Componentes de Estado (useState)

Componentes de Estado (useState)

Seja bem vindo ao FUTURO!

O conteúdo que você vai aprender agora está relacionado com a forma com que iremos criar nossos componentes daqui em diante.

E também (assim eu espero) que seja o seu método preferido (N° 1) de se criar componentes em ReactJS.

Nesta lição, você vai aprender a criar componentes de estado! E por um ponto final nos componentes de classe e também nos componentes funcionais.

O que são Componentes de Estado?

Considerados uma parte fundamental da arquitetura das aplicações feitas em ReactJS, um componente de estado é uma evolução dos componentes funcionais, onde estes, passam a conseguirem controlar o ciclo de vida junto ao estado da nossa aplicação.

A maneira como criamos um componente de estado segue a mesma estrutura vista em componentes funcionais, ou seja, você ainda cria seus componentes dentro de constantes que executam funções anônimas onde contam com um return.

Um componente de estado, nada mais é do que uma parte de um componente feito em ReactJS que pode armazenar e gerenciar dados que podem mudar ao longo do tempo.

Sendo assim, podemos dizer que componentes de estado são considerados uma nova versão dos componentes funcionais.

A diferença é que agora, esses "componentes funcionais" possui as mesmas funcionalidades que até então, só existiam dentro de componentes de classe.

O que é um estado de fato?

Em lições anteriores, foi dito a palavra "estado" por diversas vezes, mas será mesmo que você sabe o que é um estado?

De acordo com a documentação do ReactJS, um estado nada mais é do que um conjunto de dados que determinam, o comportamento e a aparência da interface do usuário, em um determinado momento durante a execução da nossa aplicação.

Em aplicações front-end, nós temos funções que executam ações, e variáveis que armazenam dados, que por sua vez podem mudar ao longo do tempo.

Você concorda comigo que uma variável que armazena o nome do usuário ou qualquer outra informação relevante, pode ser alterada pelo programa ao longo do tempo?

E que dependendo dos tipos de dados que são armazenados, eles meio que podem influenciar o percurso da nossa aplicação?

Mas é claro que você concorda comigo (ou não)?

Em projeto futuros (que eu espero que você participe) haverão vezes em que os dados retornados do back-end serão cruciais para executar renderizações condicionais de modo a levar o seu usuário a visualizar somente aquilo que ele precisa visualizar.

Como é o caso de um usuário de nível administrador que tem acesso total, se comparado a um usuário de nível júnior que só tera acesso a pequenas áreas do nosso sistema.

No âmbito do ReactJS, um estado nada mais é do que uma forma com que a sua aplicação consegue atualizar e refletir mudanças na interface do usuário em tempo real.

Na lição que falava sobre componentes de classe, tinhamos a nossa disposição o setState que quando chamado, era capaz de atualizar os valores armazenados dentro das nossas variáveis, que por sua vez, essas variáveis estavam conectadas a interface do usuário (sendo chamadas dentro do return do JSX), e como resultado, a atualização acontecia em tempo real sem a necessidade de atualizar a página.

É como se houvesse uma espécie de "portal" que conectasse o valor armazenado dentro da variável, e o valor dessa variável que esta sendo exposta na tela (DOM), de modo que ao mudar o valor dessa variável internamente, o valor mostrado ao usuário também se alterasse.

Criando um componente de estado

A forma de se criar componentes de estado segue a mesma de um componente funcional, observe:

const MeuComponente = () => {
 return(
 <>
 <h1>Meu Componente</h1>
 </>
 )
}

export default MeuComponente;

Ou caso você queira representar isso dentro de uma função:

function MeuComponente(){
 return(
 <>
 <h1>Meu Componente</h1>
 </>
 )
}

export default MeuComponente;

Note que não muda em absolutamente nada do que já vimos até agora, certo?

Sim, mas daí você pode me perguntar: "Quando é que um componente funcional deixa de ser chamado assim e vira um componente de estado?".

O seu componente vira um componente de estado, quando você implementa alguns hooks do ReactJS como:

  • useState
  • useEffect
  • useContext
  • useReducer
  • useCallback
  • useRef
  • etc.

Mas antes de começar a colocar a mão na massa, vamos criar o nosso projeto de testes 😄

Criando seu projeto de testes

Antes de começarmos, vamos criar um novo projeto em ReactJS dedicado a esta lição. No meu caso criei um novo projeto chamado de componentes-de-estado:

npx create-react-app componentes-de-estado

Após isso, não se esqueça de fazer aquela limpeza do código, e também aquela organizada nas pastas do src (organização por funcionalidade) 😁

Em seguida vamos criar o nosso primeiro componente chamado de MeuComponente:

MeuComponente > index.jsx:

const MeuComponente = () => {
 return(
 <>
 <h1>Meu Componente</h1>
 </>
 )
}

export default MeuComponente;

Não se esqueça também de inserir o seu componente dentro do App.js:

import MeuComponente from "./components/MeuComponente";

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

Conseguiu? Então bora aprender um pouco mais sobre esse tal de useState!

useState

O useState é um hook que está presente dentro do framework do ReactJS, que permite adicionar e controlar o estado dos componentes da nossa aplicação

Ele funciona de forma bem parecida com o setState dos componentes de classe.

Para usar este tipo de Hook dentro do seu componente, você vai precisar importá-lo da seguinte forma:

import React, { useState } from 'react';

Em seguida vamos supor que dentro do MeuComponente, eu queria criar um novo estado que vai armazenar o nome de um usuário. Para isso eu poderia fazer o uso do useState da seguinte forma:

import React, { useState } from 'react';

const MeuComponente = () => {
 const [nome, setNome] = useState('Fulano');

 return(
 <>
 <h1>Meu Componente</h1>
 </>
 )
}

export default MeuComponente;

Como podemos ver no código acima, foi criado duas constantes simultanêamente chamadas de nome e setNome (Isso é conhecido como "destructuring assignment" em JavaScript).

Onde ambas armazenam uma instancia da função useState que é iniciada com o valor padrão 'Fulano'.

A primeira constante que que foi criada, refere-se ao estado atual (nome), e a segunda, nada mais é do que função que pode ser usada para atualizar esse estado (setNome).

Isso significa que sempre quando formos usar o useState do ReactJS, nós precisamos declarar nossas variáveis segundo a nomenclatura:

  • nomeDaVariavel + setNomeDaVariavel

Sendo assim, por convenção a palavra set sempre deve estar presente na declaração da segunda variável, vamos ver alguns exemplos.

Supondo que queiramos criar um novo estado que vai armazenar e controlar a idade desse nosso usuário:

const [idade, setIdade] = useState(30);

Supondo que queiramos controlar a url do site do nosso usuário:

const [site, setSite] = useState(null);//Podemos passar qualquer tipo de dado do Javascript

Supondo que queiramos controlar a lista de compras desse usuário:

const [listaDeCompras, setListaDeCompras] = useState({});//Inicia um estado com um array vazio, mas voce poderia iniciá-lo com objetos

"Será que a segunda variável que representa uma função, precisa seguir a mesma nomenclatura da primeira variável, sempre adicionando o termo set no início?".

Não, isso não é totalmente necessário, visto que você pode adotar outras nomenclaturas, por exemplo:

const [nome, setSamsung] = useState("micilini");

const [sobrenome, geforce] = useState("RTX");

Note que no segundo comando nós nem fizemos o uso do termo set.

Mas por que você deveria evitar fazer isso? Pelo fato da convenção adota por diversos desenvolvedores ao redor do mundo, onde chegamos a conclução de que manter a mesma nomenclatura da primeira variável usando o set, nos ajuda a identificar qual estado nos estamos mudando.

Ou você vai querer ficar chamando setSamsung e geforce para mudar o nome das suas variáveis nome e sobrenone? Não é mais fácil e intuitivo usar setNome ou setSobrenome? Reflita sobre isso 🧐

Agora que você já entendeu como funciona a declaração de um useState, vamos conectá-lo no HTML do nosso componnete (JSX): 

import React, { useState } from 'react';

const MeuComponente = () => {
 const [nome, setNome] = useState('Fulano');
 return(
 <>
 <h1>Olá {nome}</h1>
 </>
 )
}

export default MeuComponente;

Quando conectamos o nosso estado dentro do JSX, o ReactJS já é capaz de conectar em tempo real a variável nome com o controle de estado, de modo que se a variável nome sofrer alteração, o usuário consiga visualizar em tempo real o que foi mudado.

Vamos testar isso?

Agora nós iremos criar um botão dentro do nosso componente, que vai chamar uma função responsável por mudar o valor da variável nome:

import React, { useState } from 'react';

const MeuComponente = () => {
 const [nome, setNome] = useState('Fulano');

 const mudaNome = () => {
 setNome('Ciclano');
 }

 return(
 <>
 <h1>Olá {nome}</h1>
 <button onClick={mudaNome}>Mudar Nome</button>
 </>
 )
}

export default MeuComponente;

Note que ao clicar no botão, o atributo onClick aciona a função chamada mudaNome, que por sua vez executa o seguinte código:

setNome('Ciclano');

Como dito anteriormente, a variável setNome é uma função do useState responsável por receber um novo valor (uma nova string), que nesse caso é o Ciclano, e por de baixo dos panos atribuir esse valor para dentro da variável nome.

O que acarretará numa mudança automática na tela do usuário, veja como ficou o resultado final:

Incrível não acha? É como se os nossos componentes funcionais passassem a atuar como componentes de classe com menos linhas de código 😁

Mas lembre-se, a partir do momento que usamos estes hooks, seus componentes deixam de ser componentes funcionais e passam a ser chamados componentes de estado, ok?

Preciso usar const em um useState em vez de var ou let?

O uso do const em um useState não é estritamente necessário, mas é uma prática comum e recomendada em muitos casos.

Um dos principais motivos ao declarar o estado com const, é que você está enfatizando que o valor daquele estado não será reatribuído.

Embora o próprio estado (nossa variável) possa mudar ao longo do tempo (através da função de atualização retornada por useState), a referência à variável de estado em si permanece constante (const).

Isso ajuda a prevenir erros sutis relacionados à mutabilidade e ainda facilita o rastreamento de bugs.

Ademais, o uso do const garante que a variável de estado esteja limitada ao escopo do bloco onde foi declarada, além é claro, de ser considerada uma boa prática segundo a documentação do ReactJS.

Portanto, sempre declare seus useState usando uma variável do tipo const.

Mais exemplos do useState

Para você ficar fera e dominar o useState, eu preparei mais 3 exemplos diferentes 😎

Vamos lá?

Exemplo) Tarefas Únicas

Neste exemplo, vamos criar um pequeno componente que será responsável por adicionar novas tarefas dentro de uma lista.

Para isso você deverá criar um novo componente chamado de TarefasUnicas.

TarefasUnicas > index.jsx:

import React, { useState } from 'react';

const TarefasUnicas = () => {

 const [tarefas, setTarefas] = useState([
 'Comprar uma RTX 4090 TI SUPER',
 'Começar a estudar inglês para ter a oportunidade de trabalhar fora do Brasil'
 ]);

 function adicionaTarefa(){
 setTarefas([...tarefas, 'Pare de adicionar novas tarefas!']);
 }

 return(
 <div>
 <h1>Tarefas em ReactJS</h1>
 <ul>
 {tarefas.map(tarefa => (
 <li key={tarefa}>{tarefa}</li>
 ))}
 </ul>
 <button onClick={adicionaTarefa}>Adicionar Tarefa</button>
 </div>
 );
}

export default TarefasUnicas;

No código acima declaramos um estado chamado de tarefas, que vai armazenar inicialmente 2 tarefas diferentes.

No HTML da página as tarefas são mostradas por meio do comando map, e um botão chamado Adicionar Tarefa aciona a função responsável por adicionar mais uma tarefa na lista (adicionaTarefa).

E como a atualização é em tempo real, a nossa aplicação já consegue mostrar o que foi adicionado para nosso usuário.

Veja como ficou o resultado final:

Exemplo) Contador

Neste exemplo, vamos criar um contador usando o useState, de modo que sempre que o usuário clicar no botão Aumentar, o contador some mais um a nossa variável.

Para isso vamos criar um novo componente chamado Contador.

Contador > index.jsx:

import React, { useState } from 'react';
 
const Contador = () => {
 
 const [contador, setContador] = useState(0);
 
 return (
 <div>
 <p>Você clicou {contador} vezes</p>
 <button onClick={() => setContador(contador + 1)}>
 Aumentar
 </button>
 </div>
 );
}

export default Contador;

No código acima, declaramos um novo estado chamado de contador onde ele começa com o valor 0. No HTML existe um botão chamado Aumentar que executa uma função anônima diretamente no onClick, que é responsável por aumenta o valor do contador em + 1.

Caso desejar, você pode atribuir uma função para o onClick da seguinte forma:

import React, { useState } from 'react';
 
const Contador = () => {
 
 const [contador, setContador] = useState(0);

 const addNumero = () => {
 setContador(contador + 1);
 }
 
 return (
 <div>
 <p>Você clicou {contador} vezes</p>
 <button onClick={addNumero}>
 Aumentar
 </button>
 </div>
 );
}

export default Contador;

Veja como ficou o resultado final:

Tarefas Dinâmicas

Dessa vez nós iremos criar uma lista de tarefas chamada de TarefasDinamicas, cujo o objetivo é dar a possibilidade do nosso usuário escrever o tipo de tarefa que ele deseja adicionar a lista.

TarefasDinamicas > index.jsx:

import React, { useState } from 'react';

const TarefasDinamicas = () => {

 const [tarefa, setTarefa] = useState('');
 const [tarefas, setTarefas] = useState([]);

 function adicionaTarefa(){
 setTarefas([...tarefas, tarefa]);
 setTarefa('');
 }

 return(
 <div>
 <h1>Tarefas Dinâmicas em ReactJS</h1>
 <ul>
 {tarefas.map(tarefa => (
 <li key={tarefa}>{tarefa}</li>
 ))}
 </ul>
 <input type="text" placeholder="Digite sua Tarefa" value={tarefa} onChange={(e) => setTarefa(e.target.value)} />
 <button onClick={adicionaTarefa}>Adicionar Tarefa</button>
 </div>
 );
}

export default TarefasDinamicas;

O funcionamento da lógica acima é bem similar ao visto no TarefasUnicas, a diferença é que temos mais um estado chamado de tarefa, que está conectado ao input que é do tipo text.

E aqui vem uma funcionalidade do useState que nos devemos nos atentar!

Por via de regra, sempre quando temos um estado (variável) que está conectado a um input (ou algum outro formulário), esse estado deve estar inserido dentro do atributo value e também sendo modificado dentro do atributo onChange, por exemplo:

<input type="text" placeholder="Digite sua Tarefa" value={tarefa} onChange={(e) => setTarefa(e.target.value)} />

Mas por que isso deve ser feito dessa forma?

Usar o onChange junto com value permite que o estado seja atualizado dinamicamente conforme o usuário interage com o formulário. Pois se você tentar usar somente o value da seguinte forma:

<input type="text" placeholder="Digite sua Tarefa" value={tarefa} />

E tentar digitar alguma coisa naquele input, o ReactJS não vai deixar você digitar nada 🤣

Isso aconteceu porque a variável tarefa é um estado controlado pelo ReactJS, onde seu valor inicial é sempre vazio:

const [tarefa, setTarefa] = useState('');

E como as atualizações acontecem em tempo real e tudo está meio que conectado, mesmo que você digite algum valor dentro do input, o ReactJS vai identificar que o valor do estado tarefa não mudou e continua sendo nulo, com isso ele trata de apagar (na velocidade da luz) tudo o que você esta digitando. 

E é por esse motivo que o seu input parece congelado 🥶

A solução, consiste em fazer com que toda vez que o usuário digite algo no input, uma função seja acionada para chamar o setTarefa, de modo a mudar o valor do estado tarefa com o texto que o usuário digitou no input. E como fazemos isso?

Usando o onChange que é acionado toda vez que o usuário altera o input  😄

Observe a solução em ação:

<input type="text" placeholder="Digite sua Tarefa" value={tarefa} onChange={(e) => setTarefa(e.target.value)} />

Observe também que o valor retornado pelo input, existe dentro de uma variável temporária de retorno chamada de e, onde o resultado pode ser encontrado dentro das chaves target.value.

Caso desejar, você pode chamar uma função a parte e usar o setTarefa por lá:

import React, { useState } from 'react';

const TarefasDinamicas = () => {

 const [tarefa, setTarefa] = useState('');
 const [tarefas, setTarefas] = useState([]);

 function adicionaTarefa(){
 setTarefas([...tarefas, tarefa]);
 setTarefa('');
 }

 function handleChange(event){
 setTarefa(event.target.value);
 }

 return(
 <div>
 <h1>Tarefas Dinâmicas em ReactJS</h1>
 <ul>
 {tarefas.map(tarefa => (
 <li key={tarefa}>{tarefa}</li>
 ))}
 </ul>
 <input type="text" placeholder="Digite sua Tarefa" value={tarefa} onChange={handleChange} />
 <button onClick={adicionaTarefa}>Adicionar Tarefa</button>
 </div>
 );
}

export default TarefasDinamicas;

Note que dentro da função adicionarTarefa, eu não só estou adicionando a tarefa no array, quanto também limpando o valor do input

setTarefa('');

Fiz isso apenas por comodidade, para evitar que meu usuário precise apagar o texto que ele mesmo digitou sempre quando o botão é clicado.

Veja como ficou o resultado final:

Atenção

Lembre-se: sempre quando um estado está relacionado com um input, não se esqueça de atribuir esse estado nos atributos value e fazer o uso do onChange para mudar o valor daquele estado.

Declarando múltiplos estados dentro de um mesmo objeto

Quando estamos gerenciando muitos estados, como esses do exemplo abaixo:

import React, {useState} from 'react'

const MeuComponente = () => {

	const [cep, setCep] = useState()
	const [endereco, setEndereco] = useState()
	const [cidade, setCidade] = useState()
	const [bairro, setBairro] = useState()
	const [complementos, setComplemento] = useState()
	
	....
	
}

Em vez de você declarar cada um dessses estados separadamente, você pode criar um objeto de estados da seguinte forma:

import React, {useState} from 'react'

const MeuComponente = () => {

	const [enderecoCompleto, setEnderecoCompleto] = useState({
		cep: '',
		endereco: '',
		cidade: '',
		bairro: '',
		complemento: ''
	})
	
	....
	
}

Como cada um dos estados listados acima podem fazer parte de uma categoria maior (enderecoCompleto), sendo assim, nada mais justo que juntá-los dentro de um único estado 🙂

Observe abaixo como mudar e selecionar cada um dos estados que foram declarados dentro de um objeto:

import React, { useState } from 'react';

const MeuComponente = () => {
 const [enderecoCompleto, setEnderecoCompleto] = useState({
 cep: '',
 endereco: '',
 cidade: '',
 bairro: '',
 complemento: ''
 });

 // Função para atualizar o estado do cep
 const handleChangeCep = (event) => {
 setEnderecoCompleto({ ...enderecoCompleto, cep: event.target.value });
 };

 // Função para atualizar o estado do endereço
 const handleChangeEndereco = (event) => {
 setEnderecoCompleto({ ...enderecoCompleto, endereco: event.target.value });
 };

 // Outras funções para atualizar os estados de cidade, bairro, complemento, etc.

 return (
 <div>
 <input
 type="text"
 value={enderecoCompleto.cep}
 onChange={handleChangeCep}
 placeholder="CEP"
 />
 <input
 type="text"
 value={enderecoCompleto.endereco}
 onChange={handleChangeEndereco}
 placeholder="Endereço"
 />
 {/* Inputs para outros campos do endereço */}
 </div>
 );
};

export default MeuComponente;

Observação: Falando um pouco sobre a questão da performance das nossas aplicações, quando temos muitos estados que precisam ser gerenciados, vale a pena investir em bibliotecas de formulários como o react-hook-form, ou quem sabe adotar o useRef (para acessar os valores diretamente) ou o useReducer (para lidar com estados compostos).

Mas fique tranquilo que em lições futuras iremos aprender a utilizar cada um desses comandos e bibliotecas que eu mencionei acima 😁

Quando uma variável deve ter seu estado controlado e quando não?

Se você for parar pra pensar, até o momento nós usamos os estados (=useState e setState dos componentes de classe, sempre quando queriamos conectar nossas variáveis a elementos da UI (User Interface).

No exemplos que vimos anteriormente, eu precisei controlar todos aqueles estados pois em algum momento eles seriam mostrados na tela do usuario, ou seja, eles seriam retornados dinâmicamente para a UI (User Interface).

Isso diz a respeito e que uma variável SEMPRE deve ter seu estado controlado quando sua mudança afeta a interface do usuário e/ou o comportamento do componente.

Para exemplificar isso, vamos dar uma olhada no exemplo abaixo:

import React, { useState } from 'react';

const Limite = () => {

 const limiteDoCartao = 2700;
 const [valorCompra, setValorCompra] = useState(0);
 const [foiComprado, setFoiComprado] = useState(false);
 const [limiteUltrapassado, setLimiteUltrapassado] = useState(false);

 function handleChange(event){
 setValorCompra(event.target.value);
 if(event.target.value > limiteDoCartao){
 setFoiComprado(false);
 setLimiteUltrapassado(true);
 return;
 }
 setFoiComprado(true);
 setLimiteUltrapassado(false);
 }

 return(
 <div>
 <p>Qual é o valor da compra do produto que você está querendo comprar? (Seu limite de crédito é de R${limiteDoCartao})</p>
 <input type="number" placeholder="Digite o valor da compra" value={valorCompra} onChange={handleChange} />

 {valorCompra <= 0 && <p style={{ color: 'gray' }}>Insira o valor de compra no campo acima!</p>}

 {foiComprado && <p style={{ color: 'green' }}>Você acabou de comprar um produto no valor de R$ {valorCompra}</p>}

 {limiteUltrapassado && <p style={{ color: 'red' }}>O valor do produto ultrapassa o limite de compra! (R$ {valorCompra}/{limiteDoCartao})</p>}

 </div>
 );
}

export default Limite;

O código acima nada mais é do que uma pequena aplicação que verifica se o usuário pode comprar o produto de acordo com seu limite de crédito.

Se você analisar o código dessa aplicação, verá que a variável limiteDoCartao é a única que não tem seu estado controlado, que apesar de estar sendo chamada na UI (User Interface), seu valor nunca será modificado durante a execução do programa.

Portanto, não faz o menor sentido controlar o estado da variável limiteDoCartao.

Vamos pegar agora um outro exemplo similar a aplicação que acabamos de construir!

import React, { useState } from 'react';

const LimiteComBug = () => {

 var limiteDoCartao = 2700;
 const [valorCompra, setValorCompra] = useState(0);
 const [foiComprado, setFoiComprado] = useState(false);
 const [limiteUltrapassado, setLimiteUltrapassado] = useState(false);

 function handleChange(event){
 if(event.target.value === 57426983223){
 limiteDoCartao = 7800;
 alert('Limite aumentado para R$ 7.800,00');
 }
 setValorCompra(event.target.value);
 if(event.target.value > limiteDoCartao){
 setFoiComprado(false);
 setLimiteUltrapassado(true);
 return;
 }
 setFoiComprado(true);
 setLimiteUltrapassado(false);
 console.log('Limite do Cartão: ' + limiteDoCartao);
 }

 return(
 <div>
 <p>Qual é o valor da compra do produto que você está querendo comprar? (Seu limite de crédito é de R${limiteDoCartao})</p>
 <input type="number" placeholder="Digite o valor da compra" value={valorCompra} onChange={handleChange} />

 {valorCompra <= 0 && <p style={{ color: 'gray' }}>Insira o valor de compra no campo acima!</p>}

 {foiComprado && <p style={{ color: 'green' }}>Você acabou de comprar um produto no valor de R$ {valorCompra}</p>}

 {limiteUltrapassado && <p style={{ color: 'red' }}>O valor do produto ultrapassa o limite de compra! (R$ {valorCompra}/{limiteDoCartao})</p>}

 </div>
 );
}

export default LimiteComBug;

A aplicação acima é a mesma da anterior, a diferença é que mudamos o tipo de variável limiteDoCartao para let - o que possibilita mudar seu valor -, além disso, nós adicionamos um easter egg que aumenta o limite do usuário (de 2700 para 7800) sempre quando ele digitar uma numeração especifica dentro do input (57426983223).

Mas apesar disso tudo, parece que o ReactJS não conseguiu mudar o valor da variável limiteDoCartao mesmo digitando a numeração, mas por que?

O problema ocorreu, porque você está tentando atualizar uma variável local (limiteDoCartao) diretamente dentro da função handleChange, o que não é suficiente para atualizar o valor daquela variável.

Uma vez que o ReactJS não está ciente dessa mudança, e por conta disso, ele não renderiza o componente novamente com o novo valor.

A verdade é que o ReactJS tem uma forma diferente de se trabalhar, pois analisando pela lógica do Javascript, a variável limiteDoCartao deveria SIM ter tido o seu valor alterado, possibilitando que o usuário conseguisse informar no input valores acima de 2700 e abaixo de 7800.

Por de baixo dos panos, quando você declara uma variável como limiteDoCartao fora do escopo do componente (ou seja, dentro do corpo da função LimiteComBug), essa variável é tratada como uma variável local ao escopo da função.

Isso significa que ela não está associada ao estado do componente, e alterações que acontecem nessa variável não causam uma nova renderização do componente, fazendo com que não haja uma atualização do estado do componente, resultando em apenas uma mudança na variável local.

Nesse sentido, podemos dizer que tecnicamente a variável limiteDoCartao teve seu valor alterado SIM!

E embora a variável limiteDoCartao tenha sido alterada dentro do contexto do ReactJS, a mudança não foi propagada para a interface do usuário porque não foi feita por meio de um hook de estado na qual informasse ao ReactJS que ele deveria re-renderizar o componente. 

Em resumo, o ReactJS não reage automaticamente a mudanças em variáveis locais, mas sim às mudanças de estado que são gerenciadas explicitamente por hooks de estado, como é o caso do useState ou setState (componentes de classe).

Sendo assim, para resolver aquele problema, nós somos obrigados a transformar a variável limiteDoCartao em um estado da seguinte forma:

import React, { useState } from 'react';

const LimiteComBug = () => {
 const [limiteDoCartao, setLimiteDoCartao] = useState(2700);
 const [valorCompra, setValorCompra] = useState(0);
 const [foiComprado, setFoiComprado] = useState(false);
 const [limiteUltrapassado, setLimiteUltrapassado] = useState(false);

 function handleChange(event){
 if(event.target.value === 57426983223){
 setLimiteDoCartao(7800);
 alert('Limite aumentado para R$ 7.800,00');
 }
 setValorCompra(event.target.value);
 if(event.target.value > limiteDoCartao){
 setFoiComprado(false);
 setLimiteUltrapassado(true);
 return;
 }
 setFoiComprado(true);
 setLimiteUltrapassado(false);
 console.log('Limite do Cartão: ' + limiteDoCartao);
 }

 return(
 <div>
 <p>Qual é o valor da compra do produto que você está querendo comprar? (Seu limite de crédito é de R${limiteDoCartao})</p>
 <input type="number" placeholder="Digite o valor da compra" value={valorCompra} onChange={handleChange} />

 {valorCompra <= 0 && <p style={{ color: 'gray' }}>Insira o valor de compra no campo acima!</p>}

 {foiComprado && <p style={{ color: 'green' }}>Você acabou de comprar um produto no valor de R$ {valorCompra}</p>}

 {limiteUltrapassado && <p style={{ color: 'red' }}>O valor do produto ultrapassa o limite de compra! (R$ {valorCompra}/{limiteDoCartao})</p>}

 </div>
 );
}

export default LimiteComBug;

Note que até o valor do limite foi alterado na mensagem acima quando digitamos o número mágico 😉

Arquivos da lição

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

Conclusão

Nesta lição você aprendeu um pouco mais sobre o que são componentes de estado, e como fazer o uso do useState para controlar o estado da sua aplicação.

Na próxima aula daremos uma olhada no funcionamento do useCallback.