Objetos em Javascript e React

Nesse artigo vou te mostrar um "segredo" de objetos em javascript e como não saber disso limita seu conhecimento sobre states.

reactjavascript

Assunto chato

Passei anos ensinando React em cursos livres e universidades, além de outros tantos anos ensinando e auxiliando iniciantes nas empresas em que trabalhei. Desde então alguns erros me parecem bem constantes, alguns por conta da complexidade do React, outros por falta de base de Javascript. No final do dia React ainda é Javascript e creio ser necessário dominar ambos, mas me parece que conteúdo mais teórico, ou sem uma aplicação prática imediata não gera engajamento.

Parece que é muito mais descolado tacar um useMemo e sair falando memoização do que entender como de fato as coisas funcionam.

O cenário

Vamos começar com dois códigos de exemplo, sugiro aqui que você pare um tempo para ler cada código e executa-lo de cabeça, faça um exercício mental para visualizar a saída de cada um deles.

Count1.jsx
// codigo1
function Count1() {
  const inicial = [1, 2, 3];
  const [contador, setContador] = useState(1);
  const [lista, setLista] = useState(inicial);
 
  function mudarTudao() {
    inicial.push(4);
    setContador(2);
    setLista(inicial);
  }
 
  return (
    <div>
      <span>Contador: {contador}</span>
      <ul>{lista && lista.map((v, i) => <li key={i}>{v}</li>)}</ul>
      <button onClick={mudarTudao}>Mudar tudão</button>
    </div>
  );
}
Count2.jsx
function Count2() {
  const inicial = [1, 2, 3];
  const [contador, setContador] = useState(1);
  const [lista, setLista] = useState(inicial);
 
  function mudarTudao() {
    inicial.push(4);
    //setContador(2);
    setLista(inicial);
  }
 
  return (
    <div>
      <span>Contador: {contador}</span>
      <ul>{lista && lista.map((v, i) => <li key={i}>{v}</li>)}</ul>
      <button onClick={mudarTudao}>Mudar tudão</button>
    </div>
  );
}

A diferença escrita entre os códigos é apenas a linha 9. Porém a diferença de saída é muito maior, enquanto o Count1 vai renderizar uma lista de 1 ao 4, o Count2 não, ele irá manter a renderização inicial da lista de 1 ao 3.

Você pode conferir aqui

Faça um exercício mental e tente descobrir o porquê disso.

Vamos voltar um pouco

Para explicar o problema primeiro precisamos voltar um pouco (confia!), temos que entender um pouco sobre valores primitivos e não primitivos em Javascript.

Valores primitivos

Valores (ou tipos) não primitivos são aqueles que não são objetos e não possuem métodos ou propriedades.

  • string
  • number
  • bigint
  • boolean
  • undefined
  • symbol
  • null

Todos os primitivos são imutáveis, ou seja, eles não podem ser alterados! Note estamos falando do valor primitivo e não de uma variável que recebe esse valor.

let valor = 2;
valor = 3;

Esse código atribui valores para a variável valor, logo a variável muda de estado duas vezes. Guarde essa informação!

Valores não primitivos

Em Javascript valores não primitivos (objetos e arrays) são mutáveis, podemos ver isso no código a seguir. Conseguimos mudar valores de propriedades e métodos. Note que usamos const.

const pessoa = {
  nome: 'Irmão do Jorel',
};
pessoa.nome = 'Vovo Juju';

O que o const faz é impossibilitar reatribuição de valores, ou seja, podemos considerar que torna tipos não primitivos imutáveis. Note que o próximo código irá disparar um erro.

const pessoa = {
  nome: 'Irmão do Jorel',
};
pessoa = { nome: 'Vovo Juju' };

Antes conseguimos alterar a propriedade nome pois ela é um primitivo.

A explicação

Resumo da ópera, variáveis de valores primitivos tem valores imutáveis, por isso sempre que atribuimos um novo valor ela muda de estado para aquele novo valor.

Objetos e Arrays (tipos não primitivos) são mutáveis, porém const bloqueia sua reatribuição de valor, impossibilitanto sua alteração de estado.

Sendo assim nos dois códigos apresentados no início a linha dispara uma alteração de estado para uma variável de estado do React, ou seja, não é condição necessário para re-renderizar o componente.

function mudarTudao() {
  inicial.push(4);
  //setContador(2);
  setLista(inicial);
}

Porém em Count1 temos a linha 3 que muda o estado da variável de estado inicial, que é uma condiz necessária para disparar a re-renderização do componente. Na fase de re-renderização React lê os novos valores e atualiza a variável lista.

Note que o valor de lista é alterado em ambos os casos, porém em Count2 como não há reatribuição de valores o React "não sabe" que precisa re-renderizar.

function mudarTudao() {
  inicial.push(4);
  setContador(2);
  setLista(inicial);
}

Você pode entrar aqui e alterar const para let e observar que o comportamento é completamente diferente.

A solução

E é por isso que em React quando queremo alterar o estado de um Objeto ou Array fazemos assim:

function mudarTudao() {
  inicial.push(4);
  setLista([...inicial]);
}

O spread operator cria uma nova variável com todos os valores da original, sendo assim mudamos o estado da vairável lista vai setLista. Podemos inferir também que isso ocorre porque em Javascript parâmetros são referências e com spread criamos uma nova referência.

Sim, voltamos atrás para explicar uma prática trivial em React mas agora acredito que você entende o porquê disso e ainda aprendeu um pouco mais sobre Javascript.

Referências

Acessadas em: 14/01/2024

Primitive
Object
Mutable
const