useMemo é um Hook do React que permite que você armazene em cache o resultado de um cálculo entre as renderizações.

const cachedValue = useMemo(calculateValue, dependencies)

Referência

useMemo(calculateValue, dependencies)

Chame useMemo no nível superior do seu componente para armazenar em cache um cálculo entre as renderizações:

import { useMemo } from 'react';

function TodoList({ todos, tab }) {
const visibleTodos = useMemo(
() => filterTodos(todos, tab),
[todos, tab]
);
// ...
}

Veja mais exemplos abaixo.

Parâmetros

  • calculateValue: A função que calcula o valor que você quer armazenar em cache. Ela deve ser pura, não deve receber argumentos e deve retornar um valor de qualquer tipo. O React chamará sua função durante a renderização inicial. Nas renderizações seguintes, o React retornará o mesmo valor novamente se as dependencies não tiverem mudado desde a última renderização. Caso contrário, ele chamará calculateValue, retornará seu resultado e o armazenará para que possa ser reutilizado mais tarde.

  • dependencies: A lista de todos os valores reativos referenciados dentro do código calculateValue. Valores reativos incluem props, state e todas as variáveis e funções declaradas diretamente dentro do corpo do seu componente. Se seu linter estiver configurado para React, ele verificará se cada valor reativo é especificado corretamente como uma dependência. A lista de dependências deve ter um número constante de itens e ser escrita inline, como [dep1, dep2, dep3]. O React comparará cada dependência com seu valor anterior usando a comparação Object.is.

Retorna

Na renderização inicial, useMemo retorna o resultado de chamar calculateValue sem argumentos.

Durante as próximas renderizações, ele retornará um valor já armazenado da última renderização (se as dependências não tiverem mudado) ou chamará calculateValue novamente e retornará o resultado que calculateValue retornou.

Ressalvas

  • useMemo é um Hook, então você só pode chamá-lo no nível superior do seu componente ou dos seus próprios Hooks. Você não pode chamá-lo dentro de loops ou condições. Se você precisar disso, extraia um novo componente e mova o state para ele.
  • Em Strict Mode, o React chamará sua função de cálculo duas vezes para ajudá-lo a encontrar impurezas acidentais. Este é um comportamento apenas de desenvolvimento e não afeta a produção. Se sua função de cálculo for pura (como deveria ser), isso não deve afetar sua lógica. O resultado de uma das chamadas será ignorado.
  • O React não vai descartar o valor armazenado em cache, a menos que haja um motivo específico para isso. Por exemplo, em desenvolvimento, o React descarta o cache quando você edita o arquivo do seu componente. Tanto em desenvolvimento quanto em produção, o React descartará o cache se seu componente suspender durante a montagem inicial. No futuro, o React pode adicionar mais recursos que se beneficiam de descartar o cache - por exemplo, se o React adicionar suporte embutido para listas virtualizadas no futuro, faria sentido descartar o cache para itens que saem da janela de visualização da tabela virtualizada. Isso deve ser bom se você confiar no useMemo apenas como uma otimização de desempenho. Caso contrário, uma variável de state ou uma ref pode ser mais apropriada.

Note

Armazenar em cache valores de retorno como este também é conhecido como memoization, e é por isso que este Hook é chamado useMemo.


Uso

Ignorando cálculos caros

Para armazenar em cache um cálculo entre as renderizações, envolva-o em uma chamada useMemo no nível superior do seu componente:

import { useMemo } from 'react';

function TodoList({ todos, tab, theme }) {
const visibleTodos = useMemo(() => filterTodos(todos, tab), [todos, tab]);
// ...
}

Você precisa passar duas coisas para useMemo:

  1. Uma função de cálculo que não recebe argumentos, como () =>, e retorna o que você queria calcular.
  2. Uma lista de dependências incluindo cada valor dentro do seu componente que é usado dentro do seu cálculo.

Na renderização inicial, o valor que você obterá de useMemo será o resultado da chamada da sua cálculo.

Em cada renderização subsequente, o React comparará as dependências com as dependências que você passou durante a última renderização. Se nenhuma das dependências tiver mudado (comparada com Object.is), useMemo retornará o valor que você já calculou antes. Caso contrário, o React irá executar seu cálculo e retornar o novo valor.

Em outras palavras, useMemo armazena em cache um resultado de cálculo entre as renderizações até que suas dependências mudem.

Vamos analisar um exemplo para ver quando isso é útil.

Por padrão, o React irá executar todo o corpo do seu componente toda vez que ele renderizar novamente. Por exemplo, se este TodoList atualizar seu state ou receber novas props de seu pai, a função filterTodos será executada novamente:

function TodoList({ todos, tab, theme }) {
const visibleTodos = filterTodos(todos, tab);
// ...
}

Normalmente, isso não é um problema porque a maioria dos cálculos é muito rápida. No entanto, se você estiver filtrando ou transformando uma array grande ou fazendo alguma computação cara, talvez você queira pular fazê-lo novamente se os dados não tiverem mudado. Se ambos todos e tab forem os mesmos do que eram durante a última renderização, envolver o cálculo em useMemo como antes permite que você reutilize visibleTodos que você já calculou antes.

Este tipo de armazenamento em cache é chamado de memoization.

Note

Você só deve confiar em useMemo como uma otimização de desempenho. Se seu código não funcionar sem ele, encontre o problema subjacente e corrija-o primeiro. Então você pode adicionar useMemo para melhorar o desempenho.

Deep Dive

Como saber se um cálculo é caro?

Em geral, a menos que você esteja criando ou percorrendo milhares de objetos, provavelmente não é caro. Se você quiser ter mais confiança, pode adicionar um console log para medir o tempo gasto em um trecho de código:

console.time('filter array');
const visibleTodos = filterTodos(todos, tab);
console.timeEnd('filter array');

Execute a interação que você está medindo (por exemplo, digitar na entrada). Você verá logs como filter array: 0.15ms no seu console. Se o tempo total registrado somar uma quantidade significativa (digamos, 1ms ou mais), pode fazer sentido memorizar esse cálculo. Como um experimento, você pode então envolver o cálculo em useMemo para verificar se o tempo total registrado diminuiu para aquela interação ou não:

console.time('filter array');
const visibleTodos = useMemo(() => {
return filterTodos(todos, tab); // Ignorado se todos e tab não mudaram
}, [todos, tab]);
console.timeEnd('filter array');

useMemo não vai tornar a primeira renderização mais rápida. Ele só ajuda você a pular trabalho desnecessário em atualizações.

Tenha em mente que sua máquina provavelmente é mais rápida do que a de seus usuários, então é uma boa ideia testar o desempenho com uma lentidão artificial. Por exemplo, o Chrome oferece uma opção de CPU Throttling para isso.

Observe também que medir o desempenho no desenvolvimento não fornecerá os resultados mais precisos. (Por exemplo, quando o Strict Mode estiver ativado, você verá cada componente renderizar duas vezes em vez de uma.) Para obter os tempos mais precisos, crie seu aplicativo para produção e teste-o em um dispositivo como seus usuários têm.

Deep Dive

Você deve adicionar useMemo em todos os lugares?

Se seu app for como este site e a maioria das interações for grosseira (como substituir uma página ou uma seção inteira), a memoization geralmente é desnecessária. Por outro lado, se seu app for mais parecido com um editor de desenho e a maioria das interações for granular (como mover formas), você pode achar a memoization muito útil.

Otimizar com useMemo só é valioso em alguns casos:

  • O cálculo que você está colocando em useMemo é notavelmente lento e suas dependências raramente mudam.
  • Você o passa como uma prop para um componente encapsulado em memo. Você quer pular a re-renderização se o valor não tiver mudado. A memoization permite que seu componente renderize novamente apenas quando as dependências não são as mesmas.
  • O valor que você está passando é posteriormente usado como uma dependência de algum Hook. Por exemplo, talvez outro valor de cálculo useMemo dependa dele. Ou talvez você esteja dependendo desse valor de useEffect.

Não há nenhum benefício em encapsular um cálculo em useMemo em outros casos. Também não há nenhum dano significativo em fazer isso, então algumas equipes optam por não pensar em casos individuais e memorizar o máximo possível. A desvantagem dessa abordagem é que o código se torna menos legível. Além disso, nem toda memoization é eficaz: um único valor que é “sempre novo” é suficiente para quebrar a memoization para um componente inteiro.

Na prática, você pode tornar muita memoization desnecessária seguindo alguns princípios:

  1. Quando um componente envolve visualmente outros componentes, deixe-o aceitar JSX como filhos. Dessa forma, quando o componente wrapper atualiza seu próprio state, o React sabe que seus filhos não precisam renderizar novamente.
  2. Prefira o state local e não eleve o state mais do que o necessário. Por exemplo, não mantenha o state transitório, como formulários e se um item está em hover, no topo da sua árvore ou em uma biblioteca de state global.
  3. Mantenha sua lógica de renderização pura. Se renderizar novamente um componente causar um problema ou produzir algum artefato visual perceptível, é um erro no seu componente! Corrija o erro em vez de adicionar memoization.
  4. Evite Effects desnecessários que atualizam o state. A maioria dos problemas de desempenho em aplicativos React é causada por cadeias de atualizações originadas de Effects que fazem seus componentes renderizarem repetidamente.
  5. Tente remover dependências desnecessárias de seus Effects. Por exemplo, em vez de memoization, muitas vezes é mais simples mover algum objeto ou uma função dentro de um Effect ou fora do componente.

Se uma interação específica ainda parecer lenta, use o React Developer Tools profiler para ver quais componentes se beneficiariam mais da memoization e adicione memoization onde for necessário. Esses princípios tornam seus componentes mais fáceis de depurar e entender, por isso é bom segui-los em qualquer caso. A longo prazo, estamos pesquisando fazer memoization granularmente automaticamente para resolver isso de uma vez por todas.

A diferença entre useMemo e calcular um valor diretamente

Example 1 of 2:
Ignorando o recálculo com useMemo

Neste exemplo, a implementação filterTodos é artificialmente retardada para que você possa ver o que acontece quando alguma função JavaScript que você está chamando durante a renderização é genuinamente lenta. Tente alternar as guias e alternar o tema.

Alternar as guias parece lento porque força o filterTodos desacelerado a ser reexecutado. Isso é esperado porque a tab mudou e, portanto, todo o cálculo precisa ser executado novamente. (Se você está curioso por que ele é executado duas vezes, é explicado aqui.)

Alterne o tema. Graças ao useMemo, ele é rápido apesar da lentidão artificial! A chamada filterTodos lenta foi ignorada porque tanto todos quanto tab (que você passa como dependências para useMemo) não mudaram desde a última renderização.

import { useMemo } from 'react';
import { filterTodos } from './utils.js'

export default function TodoList({ todos, theme, tab }) {
  const visibleTodos = useMemo(
    () => filterTodos(todos, tab),
    [todos, tab]
  );
  return (
    <div className={theme}>
      <p><b>Note: <code>filterTodos</code> is artificially slowed down!</b></p>
      <ul>
        {visibleTodos.map(todo => (
          <li key={todo.id}>
            {todo.completed ?
              <s>{todo.text}</s> :
              todo.text
            }
          </li>
        ))}
      </ul>
    </div>
  );
}


Ignorando a nova renderização de componentes

Em alguns casos, useMemo também pode ajudá-lo a otimizar o desempenho da nova renderização de componentes filhos. Para ilustrar isso, digamos que este componente TodoList passe o visibleTodos como uma prop para o componente filho List:

export default function TodoList({ todos, tab, theme }) {
// ...
return (
<div className={theme}>
<List items={visibleTodos} />
</div>
);
}

Você notou que alternar a prop theme trava o aplicativo por um momento, mas se você remover <List /> do seu JSX, ele parece rápido. Isso indica que vale a pena tentar otimizar o componente List.

Por padrão, quando um componente renderiza novamente, o React renderiza novamente todos os seus filhos recursivamente. É por isso que, quando TodoList renderiza novamente com um theme diferente, o componente List também renderiza novamente. Isso é bom para componentes que não exigem muito cálculo para renderizar novamente. Mas se você verificou que uma nova renderização é lenta, pode dizer ao List para pular a nova renderização quando suas props forem as mesmas da última renderização, envolvendo-o em memo:

import { memo } from 'react';

const List = memo(function List({ items }) {
// ...
});

Com essa alteração, List pulará a nova renderização se todas as suas props forem iguais às da última renderização. É aqui que o armazenamento em cache do cálculo se torna importante! Imagine que você calculou visibleTodos sem useMemo:

export default function TodoList({ todos, tab, theme }) {
// Sempre que o tema muda, esta será uma array diferente...
const visibleTodos = filterTodos(todos, tab);
return (
<div className={theme}>
{/* ...então as props do List nunca serão as mesmas e ele irá renderizar novamente toda vez */}
<List items={visibleTodos} />
</div>
);
}

No exemplo acima, a função filterTodos sempre cria uma array diferente, semelhante à forma como o literal de objeto {} sempre cria um novo objeto. Normalmente, isso não seria um problema, mas significa que as props de List nunca serão as mesmas, e sua otimização memo não funcionará. É aqui que useMemo é útil:

export default function TodoList({ todos, tab, theme }) {
// Diga ao React para armazenar em cache seu cálculo entre novas renderizações...
const visibleTodos = useMemo(
() => filterTodos(todos, tab),
[todos, tab] // ...para que, desde que essas dependências não mudem...
);
return (
<div className={theme}>
{/* ...List receberá as mesmas props e poderá pular a nova renderização */}
<List items={visibleTodos} />
</div>
);
}

Ao encapsular o cálculo visibleTodos em useMemo, você garante que ele tenha o mesmo valor entre as novas renderizações (até que as dependências mudem). Você não precisa encapsular um cálculo em useMemo, a menos que o faça por algum motivo específico. Neste exemplo, a razão é que você o passa para um componente encapsulado em memo, e isso permite que ele pule a nova renderização. Existem algumas outras razões para adicionar useMemo, que são descritas mais adiante nesta página.

Deep Dive

Memorizando nós JSX individuais

Em vez de encapsular List em memo, você pode encapsular o próprio nó JSX <List /> em useMemo:

export default function TodoList({ todos, tab, theme }) {
const visibleTodos = useMemo(() => filterTodos(todos, tab), [todos, tab]);
const children = useMemo(() => <List items={visibleTodos} />, [visibleTodos]);
return (
<div className={theme}>
{children}
</div>
);
}

O comportamento seria o mesmo. Se os visibleTodos não mudaram, List não será renderizado novamente.

Um nó JSX como <List items={visibleTodos} /> é um objeto como { type: List, props: { items: visibleTodos } }. Criar este objeto é muito barato, mas o React não sabe se seu conteúdo é o mesmo da última vez ou não. É por isso que, por padrão, o React irá renderizar novamente o componente List.

No entanto, se o React vê o mesmo JSX exato que durante a renderização anterior, ele não tentará renderizar seu componente novamente. Isso ocorre porque os nós JSX são imutáveis. Um objeto de nó JSX não poderia ter mudado com o tempo, então o React sabe que é seguro pular uma nova renderização. No entanto, para que isso funcione, o nó tem que realmente ser o mesmo objeto, não apenas ter a mesma aparência no código. É isso que o useMemo faz neste exemplo.

Envolver manualmente nós JSX em useMemo não é conveniente. Por exemplo, você não pode fazer isso condicionalmente. É por isso que geralmente você envolveria componentes com memo em vez de envolver nós JSX.

A diferença entre pular as renderizações e sempre renderizar novamente

Example 1 of 2:
Pular a renderização novamente com useMemo e memo

Neste exemplo, o componente List é artificialmente atrasado para que você possa ver o que acontece quando um componente React que você está renderizando é realmente lento. Tente alternar as abas e alternar o tema.

Alternar as abas parece lento porque força o List atrasado a renderizar novamente. Isso é esperado porque a tab mudou e, portanto, você precisa refletir a nova escolha do usuário na tela.

Em seguida, tente alternar o tema. Graças ao useMemo em conjunto com memo, é rápido, apesar da lentidão artificial! O List pulou a renderização novamente porque o array visibleTodos não foi alterado desde a última renderização. O array visibleTodos não foi alterado porque tanto todos quanto tab (que você passa como dependências para useMemo) não foram alterados desde a última renderização.

import { useMemo } from 'react';
import List from './List.js';
import { filterTodos } from './utils.js'

export default function TodoList({ todos, theme, tab }) {
  const visibleTodos = useMemo(
    () => filterTodos(todos, tab),
    [todos, tab]
  );
  return (
    <div className={theme}>
      <p><b>Note: <code>List</code> is artificially slowed down!</b></p>
      <List items={visibleTodos} />
    </div>
  );
}


Impedindo que um Effect dispare com muita frequência

Às vezes, você pode querer usar um valor dentro de um Effect:

function ChatRoom({ roomId }) {
const [message, setMessage] = useState('');

const options = {
serverUrl: 'https://localhost:1234',
roomId: roomId
}

useEffect(() => {
const connection = createConnection(options);
connection.connect();
// ...

Isso cria um problema. Cada valor reativo deve ser declarado como uma dependência do seu Effect. No entanto, se você declarar options como dependência, isso fará com que seu Effect se reconecte constantemente ao chat room:

useEffect(() => {
const connection = createConnection(options);
connection.connect();
return () => connection.disconnect();
}, [options]); // 🔴 Problem: This dependency changes on every render
// ...

Para resolver isso, você pode envolver o objeto que você precisa chamar de um Effect em useMemo:

function ChatRoom({ roomId }) {
const [message, setMessage] = useState('');

const options = useMemo(() => {
return {
serverUrl: 'https://localhost:1234',
roomId: roomId
};
}, [roomId]); // ✅ Only changes when roomId changes

useEffect(() => {
const connection = createConnection(options);
connection.connect();
return () => connection.disconnect();
}, [options]); // ✅ Only changes when options changes
// ...

Isso garante que o objeto options seja o mesmo entre as novas renderizações se useMemo retornar o objeto em cache.

No entanto, como useMemo é uma otimização de desempenho, não uma garantia semântica, o React pode descartar o valor armazenado em cache se houver uma razão específica para fazê-lo. Isso também fará com que o efeito seja disparado novamente, portanto, é ainda melhor remover a necessidade de uma dependência de função movendo seu objeto dentro do Effect:

function ChatRoom({ roomId }) {
const [message, setMessage] = useState('');

useEffect(() => {
const options = { // ✅ No need for useMemo or object dependencies!
serverUrl: 'https://localhost:1234',
roomId: roomId
}

const connection = createConnection(options);
connection.connect();
return () => connection.disconnect();
}, [roomId]); // ✅ Only changes when roomId changes
// ...

Agora seu código é mais simples e não precisa de useMemo. Saiba mais sobre como remover dependências de Effect.

Memoizando uma dependência de outro Hook

Suponha que você tenha um cálculo que dependa de um objeto criado diretamente no corpo do componente:

function Dropdown({ allItems, text }) {
const searchOptions = { matchMode: 'whole-word', text };

const visibleItems = useMemo(() => {
return searchItems(allItems, searchOptions);
}, [allItems, searchOptions]); // 🚩 Caution: Dependency on an object created in the component body
// ...

Depender de um objeto como esse anula a utilidade da memoização. Quando um componente renderiza novamente, todo o código diretamente dentro do corpo do componente é executado novamente. As linhas de código que criam o objeto searchOptions também serão executadas em cada nova renderização. Como searchOptions é uma dependência da sua chamada useMemo e é diferente toda vez, o React sabe que as dependências são diferentes e recalcula searchItems toda vez.

Para corrigir isso, você pode memoizar o próprio objeto searchOptions antes de passá-lo como dependência:

function Dropdown({ allItems, text }) {
const searchOptions = useMemo(() => {
return { matchMode: 'whole-word', text };
}, [text]); // ✅ Only changes when text changes

const visibleItems = useMemo(() => {
return searchItems(allItems, searchOptions);
}, [allItems, searchOptions]); // ✅ Only changes when allItems or searchOptions changes
// ...

No exemplo acima, se o text não mudar, o objeto searchOptions também não mudará. No entanto, uma correção ainda melhor é mover a declaração do objeto searchOptions dentro da função de cálculo useMemo:

function Dropdown({ allItems, text }) {
const visibleItems = useMemo(() => {
const searchOptions = { matchMode: 'whole-word', text };
return searchItems(allItems, searchOptions);
}, [allItems, text]); // ✅ Only changes when allItems or text changes
// ...

Agora seu cálculo depende diretamente de text (que é uma string e não pode “acidentalmente” se tornar diferente).


Memoizando uma função

Suponha que o componente Form esteja envolvido em memo. Você quer passar uma função para ele como uma prop:

export default function ProductPage({ productId, referrer }) {
function handleSubmit(orderDetails) {
post('/product/' + productId + '/buy', {
referrer,
orderDetails
});
}

return <Form onSubmit={handleSubmit} />;
}

Assim como {} cria um objeto diferente, declarações de função como function() {} e expressões como () => {} produzem uma função diferente em cada nova renderização. Sozinho, criar uma nova função não é um problema. Isso não é algo a ser evitado! No entanto, se o componente Form for memoizado, presume-se que você deseja pular a renderização dele quando nenhuma prop foi alterada. Uma prop que é sempre diferente frustraria o propósito da memoização.

Para memoizar uma função com useMemo, sua função de cálculo teria que retornar outra função:

export default function Page({ productId, referrer }) {
const handleSubmit = useMemo(() => {
return (orderDetails) => {
post('/product/' + productId + '/buy', {
referrer,
orderDetails
});
};
}, [productId, referrer]);

return <Form onSubmit={handleSubmit} />;
}

Isso parece desajeitado! Memoizar funções é comum o suficiente que o React possui um Hook embutido especificamente para isso. Envolva suas funções em useCallback em vez de useMemo para evitar ter que escrever uma função aninhada extra:

export default function Page({ productId, referrer }) {
const handleSubmit = useCallback((orderDetails) => {
post('/product/' + productId + '/buy', {
referrer,
orderDetails
});
}, [productId, referrer]);

return <Form onSubmit={handleSubmit} />;
}

Os dois exemplos acima são completamente equivalentes. A única vantagem do useCallback é que ele permite que você evite escrever uma função aninhada extra por dentro. Ele não faz mais nada. Leia mais sobre useCallback.


Solução de problemas

Meu cálculo é executado duas vezes em cada nova renderização

No Strict Mode, o React chamará algumas de suas funções duas vezes em vez de uma:

function TodoList({ todos, tab }) {
// Esta função componente será executada duas vezes para cada renderização.

const visibleTodos = useMemo(() => {
// Este cálculo será executado duas vezes se alguma das dependências mudar.
return filterTodos(todos, tab);
}, [todos, tab]);

// ...

Isso é esperado e não deve quebrar seu código.

Este comportamento apenas para desenvolvimento ajuda você a manter os componentes puros. O React usa o resultado de uma das chamadas e ignora o resultado da outra chamada. Contanto que seu componente e as funções de cálculo sejam puros, isso não deve afetar sua lógica. No entanto, se eles forem acidentalmente impuros, isso o ajudará a notar e corrigir o erro.

Por exemplo, esta função de cálculo impura muta uma array que você recebeu como prop:

const visibleTodos = useMemo(() => {
// 🚩 Erro: mutando uma prop
todos.push({ id: 'last', text: 'Go for a walk!' });
const filtered = filterTodos(todos, tab);
return filtered;
}, [todos, tab]);

O React chama sua função duas vezes, então você notaria que o todo é adicionado duas vezes. Seu cálculo não deve alterar nenhum objeto existente, mas tudo bem alterar quaisquer objetos novos que você criou durante o cálculo. Por exemplo, se a função filterTodos sempre retornar um array diferente, você pode mutar esse array em vez disso:

const visibleTodos = useMemo(() => {
const filtered = filterTodos(todos, tab);
// ✅ Correto: mutando um objeto que você criou durante o cálculo
filtered.push({ id: 'last', text: 'Go for a walk!' });
return filtered;
}, [todos, tab]);

Leia mantendo os componentes puros para saber mais sobre pureza.

Além disso, confira os guias sobre atualização de objetos e atualização de arrays sem mutação.


Minha chamada useMemo é suposta retornar um objeto, mas retorna undefined

Este código não funciona:

// 🔴 Você não pode retornar um objeto de uma arrow function com () => {
const searchOptions = useMemo(() => {
matchMode: 'whole-word',
text: text
}, [text]);

Em JavaScript, () => { inicia o corpo da função de seta, então a chave { não faz parte do seu objeto. É por isso que ele não retorna um objeto e leva a erros. Você pode corrigi-lo adicionando parênteses como ({ e }):

// Isso funciona, mas é fácil para alguém quebrar novamente
const searchOptions = useMemo(() => ({
matchMode: 'whole-word',
text: text
}), [text]);

No entanto, isso ainda é confuso e fácil demais para alguém quebrar removendo os parênteses.

Para evitar esse erro, escreva uma instrução return explicitamente:

// ✅ Isso funciona e é explícito
const searchOptions = useMemo(() => {
return {
matchMode: 'whole-word',
text: text
};
}, [text]);

Toda vez que meu componente renderiza, o cálculo em useMemo é executado novamente

Certifique-se de ter especificado o array de dependência como o segundo argumento!

Se você esquecer o array de dependência, useMemo executará novamente o cálculo toda vez:

function TodoList({ todos, tab }) {
// 🔴 Recalcula toda vez: nenhum array de dependência
const visibleTodos = useMemo(() => filterTodos(todos, tab));
// ...

Esta é a versão corrigida passando o array de dependência como um segundo argumento:

function TodoList({ todos, tab }) {
// ✅ Não recalcula desnecessariamente
const visibleTodos = useMemo(() => filterTodos(todos, tab), [todos, tab]);
// ...

Se isso não ajudar, o problema é que pelo menos uma de suas dependências é diferente da renderização anterior. Você pode depurar este problema registrando manualmente suas dependências no console:

const visibleTodos = useMemo(() => filterTodos(todos, tab), [todos, tab]);
console.log([todos, tab]);

Você pode clicar com o botão direito do mouse nos arrays de diferentes novas renderizações no console e selecionar “Armazenar como variável global” para ambos. Supondo que o primeiro tenha sido salvo como temp1 e o segundo tenha sido salvo como temp2, você pode usar o console do navegador para verificar se cada dependência em ambos os arrays é a mesma:

Object.is(temp1[0], temp2[0]); // A primeira dependência é a mesma entre os arrays?
Object.is(temp1[1], temp2[1]); // A segunda dependência é a mesma entre os arrays?
Object.is(temp1[2], temp2[2]); // ... e assim por diante para cada dependência ...

Quando você descobrir qual dependência quebra a memoização, encontre uma maneira de removê-la ou memorize-a também.


Preciso chamar useMemo para cada item da lista em um loop, mas não é permitido

Suponha que o componente Chart esteja envolvido em memo. Você quer pular a renderização de cada Chart na lista quando o componente ReportList renderizar novamente. No entanto, você não pode chamar useMemo em um loop:

function ReportList({ items }) {
return (
<article>
{items.map(item => {
// 🔴 Você não pode chamar useMemo em um loop como este:
const data = useMemo(() => calculateReport(item), [item]);
return (
<figure key={item.id}>
<Chart data={data} />
</figure>
);
})}
</article>
);
}

Em vez disso, extraia um componente para cada item e memorize dados para itens individuais:

function ReportList({ items }) {
return (
<article>
{items.map(item =>
<Report key={item.id} item={item} />
)}
</article>
);
}

function Report({ item }) {
// ✅ Chame useMemo no nível superior:
const data = useMemo(() => calculateReport(item), [item]);
return (
<figure>
<Chart data={data} />
</figure>
);
}

Alternativamente, você pode remover useMemo e, em vez disso, envolver o próprio Report em memo. Se a prop item não mudar, Report pulará a renderização novamente, então Chart pulará a renderização novamente também:

function ReportList({ items }) {
// ...
}

const Report = memo(function Report({ item }) {
const data = calculateReport(item);
return (
<figure>
<Chart data={data} />
</figure>
);
});