Uma das dúvidas que a galera mais tem em relação ao React e seus hooks são o useMemo e o useCallback, e ninguém sabe muito bem por que e quando utilizar esses hooks.

Então hoje eu resolvi explicar de forma bem detalhada como que funciona cada um desses conceitos, o React memo, o conceito de memoization, e também os hooks useMemo e useCallback.

Então vamo lá porque tem muita coisa pra explicar.

Um conteúdo desse, deveria até ser cobrado, mas trouxe tudo aqui pra você, de graça.

Setup

Primeiramente o que eu vou mostrar aqui eu fiz usando um projeto do create-react-app que eu chamei de project.

Ao abrir o projeto em seu editor ou IDE de preferencia (eu particularmente uso o VSCode), exclua todos os arquivos da pasta src.

Dentro da pasta src nós vamos criar alguns arquivos, o index.js , index.css e App.js.

No index.js você vai colocar o seguinte conteúdo:

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';

ReactDOM.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>,
  document.getElementById('root'),
);

No index.css vamos definir alguns padrões e colocar um “Dark Mode”

* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

body {
  width: 100%;
  min-height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
  background-color: #1e1e1e;
  color: #e1e1e1;
  font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}

Já no App.js vamos colocar o seguinte:

function App() {
  return <h1>React Memo com Emerson Brôga</h1>;
}

export default App;

Pronto, se você rodar o projeto agora, com npm start você verá algo como a imagem abaixo em seu navegador.

Setup inicial

Agora vamos implementar um botão que altera um state para que possamos fazer a aplicação renderizar novamente quando precisarmos e assim entender os conceitos.

No arquivo App.js vamos alterar para o seguinte código abaixo, importando o hook useState, inicializando um contador com o useState com o valor 0 e um console.log que mostre o valor do contador. Vamos também colocar uma função changeCounter que fará o incremento do nosso contador. E por fim um botão que chama a nossa função changeCounter . Veja o exemplo abaixo como vai ficar o App.js.

import { useState } from 'react';

function App() {
  const [counter, setCounter] = useState(0);
  console.log(`App re-render, ${counter}`);

  const changeCounter = () => {
    setCounter(counter + 1);
  };

  return (
    <div className="container">
      <h1>React Memo com Emerson Brôga</h1>
      <div className="actions">
        <button onClick={changeCounter} className="btn btn-teal">
          Re-render
        </button>
      </div>
    </div>
  );
}

export default App;

Agora vamos também adicionar mais um CSS para deixar tudo mais bonitinho.

h1 {
  text-align: center;
}

.value {
  font-size: 10vw;
  font-weight: bolder;
  text-align: center;
  display: block;
  line-height: 1;
  color: #ffdb81;
}

.actions {
  display: flex;
  justify-content: center;
  margin-top: 12px;
}

.items {
  display: flex;
  gap: 12px;
}

.btn {
  cursor: pointer;
  margin: 0 auto;
  border-radius: 8px;
  padding: 6px 12px;
}

.btn-teal {
  color: #1e1e1e;
  background-color: #5eead4;
  border: solid 1px #134e4a;
}

.btn-teal:hover {
  background-color: #2dd4bf;
}

Agora você terá algo assim e ao clicar no botão “Re-render” você verá no console o contador sendo incrementado.

Agora com botão de Re-render

Criando o problema

Agora vamos adicionar mais um componente no nosso projeto. Em um projeto real, esse novo componente seria feito em um novo arquivo, mas aqui vamos fazer no mesmo arquivo.

Então vamos lá, logo abaixo do import do useState crie um novo componente funcional chamado ShowValue que recebe como prop um value e retorna esse value em uma div.

function ShowValue({ value }) {
  console.log(`ShowValue render, ${value}`);

  return <div className="value">{value}</div>;
}

Agora vamos adicionar um novo state chamado de “value” no nosso App, uma função que muda nosso novo state e por fim o nosso novo componente. Nesse ponto o nosso src/App.js vai ficar assim.

import { useState } from 'react';

function ShowValue({ value }) {
  console.log(`ShowValue render, ${value}`);

  return <div className="value">{value}</div>;
}

function App() {
  const [counter, setCounter] = useState(0);
  const [value, setValue] = useState('ON');

  console.log(`App re-render, ${counter}`);

  const changeCounter = () => {
    setCounter(counter + 1);
  };

  const changeValue = () => {
    setValue(value === 'ON' ? 'OFF' : 'ON');
  };

  return (
    <div className="container">
      <h1>React Memo com Emerson Brôga</h1>
      <div className="items">
        <ShowValue value={value} />
      </div>
      <div className="actions">
        <button onClick={changeCounter} className="btn btn-teal">
          Re-render
        </button>

        <button onClick={changeValue} className="btn btn-sky">
          Change Value
        </button>
      </div>
    </div>
  );
}

export default App;

Agora vamos colocar um css pro nosso novo botão btn-sky.

.btn-sky {
  color: #1e1e1e;
  background-color: #7dd3fc;
  border: solid 1px #0c4a6e;
}
.btn-sky:hover {
  background-color: #38bdf8;
}

Agora nos temos 2 botões, um que atualiza o state no App e outro que atualiza o state no ShowValue. O problema que temos aqui é que ao atualizar o state no App, o ShowValue também é renderizado novamente, mesmo que nada lá mude. Clique nos botões e observe o console.log.

ON-OFF

Para evitar esse “re-render” desnecessário do componente ShowValue, precisamos de usar o React memo. Mas antes, vamos entender o que é o conceito de Memoization na computação.

Entendendo sobre Memoization

Memoization é uma técnica de otimização computacional, usada principalmente para aumentar a velocidade das aplicações computacionais, guardando os resultados de operações custosas e retornado resultados de cache quando dado o mesmo input em uma chamada futura.

Em resumo, o memoization faz o processamento que tem que fazer, adiciona o resultado a um cache e retorna esse cache nas chamadas futuras se o input for o mesmo.

Num exemplo prático, sabe quando você vai na padaria, pastelaria etc e tem uma tabela do tipo:

Memoization no mundo real

Essa tabela, seria o cache, a quantidade de pasteis seria o input e o valor total seria o retorno da função, ou seja, eu ja tenho em cache alguns resultados para alguns inputs, se uma nova quantidade (input) chegar, eu vou ter que calcular o resultado, e adicionar ao cache. Agora entra a pegadinha, você não vai querer colocar tudo em cache, pois isso seria como entupir a pastelaria de tabelas dessas, então use somente quando forem processamentos custosos e não para multiplicações simples.

Posso mostrar como criar uma função de memoization no JavaScript futuramente, deixe aí nos comentários se você tem interesse nisso.

Agora vamos para o React Memo.

Usando o React memo

Primeiramente vamos esclarecer duas coisas, a primeira delas é que o React memo e o useMemo são coisas diferentes e a segunda é que o React memo funciona de uma forma um pouco diferente do Memoization da computação tradicional.

O método memo é um higher order component, que recebe um componente e retorna um componente, e internamente ele faz o que se propõe a fazer, o memoization.

Então vamos importar o memo do react e criar uma versão “memoized” do componente ShowValue

import { useState, memo } from 'react';

function ShowValue({ value }) {
  // ... code
}

const MemoShowValue = memo(ShowValue);

function App() {
  // ... code
  return (
    <div className="container">
      <h1>React Memo com Emerson Brôga</h1>
      <div className="items">
        <MemoShowValue value={value} />
      </div>
      <div className="actions">
        <button onClick={changeCounter} className="btn btn-teal">
          Re-render
        </button>

        <button onClick={changeValue} className="btn btn-sky">
          Change Value
        </button>
      </div>
    </div>
  );
}

export default App;

Agora com o componente MemoShowValue ele só vai renderizar novamente se o value mudar.

Clique nos botões e veja o console.log.

ON OFF

Agora, vamos entender por que eu disse que o memo do react é diferente do memo da computação tradicional. Na computação tradicional, o memo vai “salvando” em cache os resultados diferentes para cada input. No React memo, ele apenas verifica se o valor atual é diferente do anterior.

Então vamos adicionar um novo MemoShowValue no nosso App.js e assim vamos conseguir visualizar o que eu acabei de dizer.

// .. code

function App() {
  // ... code
  return (
    <div className="container">
      <h1>React Memo com Emerson Brôga</h1>
      <div className="items">
        <MemoShowValue value={value} />
        <MemoShowValue value={value === 'ON' ? 'OFF' : 'ON'} />
      </div>
      <div className="actions">
        <button onClick={changeCounter} className="btn btn-teal">
          Re-render
        </button>

        <button onClick={changeValue} className="btn btn-sky">
          Change Value
        </button>
      </div>
    </div>
  );
}

export default App;

Agora que temos os dois MemoShowValue observe que mesmo depois de enviar os inputs “ON” e “OFF” os componentes sempre são renderizados quando os valores mudam, pois o React memo apenas verifica se o valor atual é diferente do anterior, enquanto um “memoization” tradicional teria em cache os resultados finais para inputs “ON” e “OFF”.

ON OFF duas vezes

Agora vamos entender o hook useMemo

Até aqui o nosso React memo, funcionou como esperado, pois as propriedades que enviamos para MemoShowValue são de valores primitivos como null, undefined, boolean, number, string, symbol, bigint, no nosso caso foi uma string.

Agora vamos remover o segundo MemoShowValue e trocar a propriedade value que é uma string para um objeto que vamos chamar de params

Agora vai ficar assim:

import { useState, memo } from 'react';

function ShowValue({ params }) {
  console.log(`ShowValue render, ${params.value}`);

  return <div className="value">{params.value}</div>;
}

const MemoShowValue = memo(ShowValue);

function App() {
  const [counter, setCounter] = useState(0);
  const [value, setValue] = useState('ON');

  console.log(`App re-render, ${counter}`);

  const changeCounter = () => {
    setCounter(counter + 1);
  };

  const changeValue = () => {
    setValue(value === 'ON' ? 'OFF' : 'ON');
  };

  return (
    <div className="container">
      <h1>React Memo com Emerson Brôga</h1>
      <div className="items">
        <MemoShowValue params={{ value }} />
      </div>
      <div className="actions">
        <button onClick={changeCounter} className="btn btn-teal">
          Re-render
        </button>

        <button onClick={changeValue} className="btn btn-sky">
          Change Value
        </button>
      </div>
    </div>
  );
}

export default App;

Nesse ponto, se a gente clicar em “Re-render” você vai ver que quebramos o nosso memoization. Pois apesar de params ser um objeto com os mesmos valores em cada renderização, a cada ciclo ele é um novo objeto. Nesse caso, precisamos do useMemo para solucionar isso, pois não se trata mais de um tipo primário. Rode o projeto e clique no botão “Re-render” e veja os resultados no console.

ON OFF memoization quebrada

Agora vamos usar o useMemo para solucionar o problema.

A primeira coisa é importar o useMemo do react e criar o nosso objeto params com ele, colocando o value como dependência. E lembrando que os hooks do react devem sempre ser chamados no inicio do componente funcional.

import { useState, useMemo, memo } from 'react';

// ... code

function App() {
  const [counter, setCounter] = useState(0);
  const [value, setValue] = useState('ON');
  const params = useMemo(() => ({ value }), [value]);

  console.log(`App re-render, ${counter}`);

  const changeCounter = () => {
    setCounter(counter + 1);
  };

  const changeValue = () => {
    setValue(value === 'ON' ? 'OFF' : 'ON');
  };

  return (
    <div className="container">
      <h1>React Memo com Emerson Brôga</h1>
      <div className="items">
        <MemoShowValue params={params} />
      </div>
      <div className="actions">
        <button onClick={changeCounter} className="btn btn-teal">
          Re-render
        </button>

        <button onClick={changeValue} className="btn btn-sky">
          Change Value
        </button>
      </div>
    </div>
  );
}

export default App;

Pronto, agora o nosso MemoShowValue volta a funcionar como esperado. Devemos usar o use memo em objetos e arrays. Clique nos botões e verifique que os re-renders acontecem como esperado.

Memoization funcionando novamente.

Vamos entender o hook useCallback do React

Agora que ja vimos o memo, o useMemo, vamos entender o useCallback, que funciona como o useMemo, porem para funções. Vamos mais uma vez quebrar nosso memoization, adicionando uma propriedade que é uma função ao nosso componente. Vamos apenas receber um onClick no nosso ShowValue e passar uma função vazia do nosso App.js.

import { useState, useMemo, memo } from 'react';

function ShowValue({ params, onClick }) {
  console.log(`ShowValue render, ${params.value}`);

  return (
    <div className="value" onClick={onClick}>
      {params.value}
    </div>
  );
}

const MemoShowValue = memo(ShowValue);

function App() {
	// ...code

  return (
    <div className="container">
      <h1>React Memo com Emerson Brôga</h1>
      <div className="items">
        <MemoShowValue params={params} onClick={() => {}} />
      </div>
      <div className="actions">
        <button onClick={changeCounter} className="btn btn-teal">
          Re-render
        </button>

        <button onClick={changeValue} className="btn btn-sky">
          Change Value
        </button>
      </div>
    </div>
  );
}

export default App;

Como você pode ver, mais uma vez quebramos o nosso “memoization”.

Memoization quebrado novamente.

Vamos corrigir esse problema, com o useCallback! Primeiramente vamos importá-lo do react e vamos criar nosso onClick do useCallback , que vai continuar sendo uma função vazia e sem nenhuma dependência. E vamos pegar essa função retornada para o nosso componente.

import { useState, useMemo, useCallback, memo } from 'react';

// ...code

function App() {
  const [counter, setCounter] = useState(0);
  const [value, setValue] = useState('ON');
  const params = useMemo(() => ({ value }), [value]);
  const onClick = useCallback(() => {}, []);

	// ...code

  return (
    <div className="container">
      <h1>React Memo com Emerson Brôga</h1>
      <div className="items">
        <MemoShowValue params={params} onClick={onClick} />
      </div>
      <div className="actions">
        <button onClick={changeCounter} className="btn btn-teal">
          Re-render
        </button>

        <button onClick={changeValue} className="btn btn-sky">
          Change Value
        </button>
      </div>
    </div>
  );
}

export default App;

Pronto agora o nosso memoization volta a funcionar como esperado.

Aplicando isso no mundo real

Primeiramente vamos lembrar que “otimização precoce é um dos males do desenvolvimento de software”. Apesar de termos essas “ferramentas” a nossa disposição devemos usá-las somente quando a gente perceber que é realmente necessário, que certas renderizações desnecessárias estão deixando a aplicação lenta. Você vai precisar do memo, useMemo e useCallback em situações bem raras, pois o react ja cuida de todas as otimizações de forma bem eficiente.

E lembrando que não há nada de errado em um componente renderizar varias vezes durante o ciclo de vida de um componente. O forma que o react trabalha internamente já é bem eficiente.

Obrigado pela leitura. Compartilhe!

Se você gostou desse post, compartilhe com seus amigos, ajude a espalhar conhecimento!

  1. Faça parte da nossa lista de Desenvolvedores
  2. Veja as dicas no Instagram
  3. Se inscreva em nosso canal do Youtube
  4. Curta nossa página no Facebook
  5. Não perca as atualizações no Twitter
  6. Siga nossos repositórios no Github

@emersonbroga

Programador há mais de 15 anos, sou formado em Desenvolvimento de Sistemas pela Faculdade Pitágoras e pós-graduado em Gestão Estratégica de Marketing pela PUC Minas. Trabalhei em diversas agências de publicidade e desenvolvimento de software e atualmente trabalho em projetos internacionais como FOX.com, FXNetworks.com, NatGeo.com entre outros. Estou atualmente dedicando meu tempo a ensinar programação em meu blog e redes sociais. Saiba mais em https://emersonbroga.com/e/sobre/.