Este contenido solo está disponible en Portugués.
Aún sin traducción para este idioma.
useMemo React: Guia Definitivo de Uso e Otimização
useMemo no React é um hook poderoso, mas frequentemente mal compreendido, levando a erros de performance. Este guia desvenda os mistérios do useMemo, explicando o array de dependências, como evitar a over-optimization, lidar com funções e aplicar a memorização inteligente. Aprenda a usar useMemo corretamente para otimizar suas aplicações.

useMemo parece simples quando você lê a documentação. "Memoriza valores caros, otimiza performance, done!"
Aí você adiciona em alguns lugares e... nada muda. Ou pior, seu app fica mais lento. Você começa a se perguntar se está fazendo algo errado ou se useMemo é só mais um hook que todo mundo fala mas ninguém explica direito.
Passei meses usando useMemo de forma errada. A documentação do React explica O QUE ele faz, mas não explica QUANDO usar, POR QUE às vezes não funciona, e como evitar os erros que todo mundo comete.
Aqui está a explicação clara que eu queria quando comecei.
Por Que useMemo Parece Tão Confuso
O problema não é você. É que useMemo tem nuances invisíveis que a documentação não explica direito:
- Array de dependências funciona diferente do que você espera
- Nem tudo deve ser memoizado (às vezes piora performance)
- Comparação de objetos não funciona como primitivos
- Timing de execução pode te surpreender
A maioria dos tutoriais mostra exemplos básicos e pula essas partes importantes. Resultado: você decora sintaxe sem entender quando realmente precisa usar.
Vamos consertar isso agora.
1. O Mistério do Array de Dependências (Por Que Às Vezes Não Funciona)
A Verdade Simples
React compara cada dependência usando Object.is(). Isso significa:
- Primitivos (string, number, boolean): Compara pelo valor ✅
- Objetos e arrays: Compara pela referência na memória ❌
Por que isso importa? Objetos e arrays são recriados a cada render, então mesmo com conteúdo idêntico, React acha que mudou.
Vamos Ver Na Prática
/* ================================================
* ❌ PROBLEMA: Por que isso não funciona?
*
* O objeto filters é RECRIADO a cada render.
* Mesmo que os valores sejam iguais, a referência
* na memória é diferente.
*
* Resultado: useMemo SEMPRE recalcula
* ================================================ */
function ListaProdutos({ filtros, produtos }) {
const produtosFiltrados = useMemo(() => {
console.log('🔄 Filtrando produtos...');
return produtos.filter(produto =>
filtros.categorias.includes(produto.categoria) &&
produto.preco >= filtros.precoMinimo
);
}, [filtros, produtos]); // ❌ filtros muda toda hora
return <Lista items={produtosFiltrados} />;
}
// Cada render cria um NOVO objeto filtros
<ListaProdutos
filtros={{ categorias: ['eletrônicos'], precoMinimo: 100 }}
produtos={produtos}
/>
O que acontece: Console mostra "🔄 Filtrando produtos..." em TODA render, mesmo quando filtros não mudaram de verdade.
✅ A Solução Que Funciona
/* ================================================
* 💡 EXPLICAÇÃO SIMPLES:
*
* Ao invés de passar objeto inteiro, extraímos
* os valores primitivos (string, number).
*
* React consegue comparar esses valores corretamente
* e só recalcula quando REALMENTE mudarem.
* ================================================ */
function ListaProdutos({ filtros, produtos }) {
// Extrair valores primitivos
const { categorias, precoMinimo } = filtros;
const categoriasString = categorias.join(','); // String é primitivo
const produtosFiltrados = useMemo(() => {
console.log('🔄 Filtrando produtos...');
return produtos.filter(produto =>
categorias.includes(produto.categoria) &&
produto.preco >= precoMinimo
);
}, [categoriasString, precoMinimo, produtos]); // ✅ Só primitivos
return <Lista items={produtosFiltrados} />;
}
Agora sim: Console só mostra "🔄 Filtrando produtos..." quando categorias ou preço REALMENTE mudarem.
Quando Isso É Útil
- Filtros de busca: Evita reprocessar lista inteira a cada digitação
- Dashboards: Cálculos caros só rodam quando dados mudam
- Formulários complexos: Validações pesadas não travam o input
⚠️ Cuidado Com
- Arrays grandes:
join()em 10.000 items pode ser caro - Objetos aninhados: Extrair todos os valores fica complexo
- Performance trade-off: Às vezes é melhor recalcular que fazer malabarismo
2. O Erro De Memoizar Tudo (Quando NÃO Usar useMemo)
A Verdade Simples
useMemo tem custo: ele precisa comparar dependências e guardar valor na memória. Se a operação for simples, o custo do useMemo é MAIOR que simplesmente recalcular.
Regra prática: Só memoize se a operação demorar mais de 1ms.
Vamos Ver O Problema
/* ================================================
* ❌ PROBLEMA: Over-optimization
*
* Essas operações são MUITO rápidas (< 0.01ms).
* useMemo está consumindo memória e fazendo comparações
* desnecessárias.
*
* Resultado: App usa mais memória sem ganho real
* ================================================ */
function PerfilUsuario({ usuario, preferencias }) {
const nomeUsuario = useMemo(() => usuario.nome, [usuario.nome]); // ❌
const idade = useMemo(() => 2024 - usuario.anoNascimento, [usuario.anoNascimento]); // ❌
const isAdmin = useMemo(() => usuario.role === 'admin', [usuario.role]); // ❌
// ✅ Esse sim vale a pena memoizar (operação cara)
const estatisticasComplexas = useMemo(() => {
return calcularAnaliseCompleta(usuario.atividade, preferencias.metricas);
}, [usuario.atividade, preferencias.metricas]);
return (
<div>
{nomeUsuario} - {idade} anos - {isAdmin ? 'Admin' : 'User'}
<GraficoEstatisticas dados={estatisticasComplexas} />
</div>
);
}
O que acontece: App usa memória extra para guardar valores que são instantâneos de calcular.
✅ O Jeito Certo
/* ================================================
* 💡 EXPLICAÇÃO SIMPLES:
*
* Operações baratas: sem useMemo
* Operações caras: com useMemo
*
* Como saber? Se demora menos de 1ms, não memoize.
* Use console.time() para medir.
* ================================================ */
function PerfilUsuario({ usuario, preferencias }) {
// ✅ Operações simples: sem useMemo
const nomeUsuario = usuario.nome;
const idade = 2024 - usuario.anoNascimento;
const isAdmin = usuario.role === 'admin';
// ✅ Operação cara: COM useMemo
const estatisticasComplexas = useMemo(() => {
console.time('Cálculo Estatísticas');
const resultado = calcularAnaliseCompleta(usuario.atividade, preferencias.metricas);
console.timeEnd('Cálculo Estatísticas'); // Mostra tempo no console
return resultado;
}, [usuario.atividade, preferencias.metricas]);
return (
<div>
{nomeUsuario} - {idade} anos - {isAdmin ? 'Admin' : 'User'}
<GraficoEstatisticas dados={estatisticasComplexas} />
</div>
);
}
Resultado: App usa menos memória e performance melhora porque não há overhead de comparações desnecessárias.
Como Decidir
✅ MEMOIZE quando:
- Processar array com 1000+ items
- Cálculos matemáticos complexos
- Transformações de dados pesadas
- Operações que demoram > 1ms
❌ NÃO MEMOIZE quando:
- Acessar propriedade simples (
user.name) - Operações matemáticas básicas (
2024 - year) - Comparações simples (
role === 'admin') - Concatenar strings curtas
Ferramenta Útil
// Hook pra medir se vale a pena memoizar
function useMemoWithProfiling(factory, deps, nome) {
return useMemo(() => {
const inicio = performance.now();
const resultado = factory();
const fim = performance.now();
console.log(`${nome}: ${(fim - inicio).toFixed(2)}ms`);
return resultado;
}, deps);
}
// Uso
const dados = useMemoWithProfiling(
() => processarDadosGrandes(rawData),
[rawData],
'Processar Dados'
);
3. Funções Que Sempre Mudam (O Problema Das Callbacks)
A Verdade Simples
Funções são objetos em JavaScript. Cada render cria uma NOVA função, mesmo que o código seja idêntico.
Isso causa re-renders em componentes filhos que usam React.memo.
Vamos Ver O Problema
/* ================================================
* ❌ PROBLEMA: Novas funções em cada render
*
* Cada render cria NOVAS funções onToggle e onDelete.
* Componentes TodoItem re-renderizam mesmo que
* o todo não tenha mudado.
*
* Resultado: Lista com 100 items = 100 re-renders
* ================================================ */
function ListaTodos({ todos, onUpdate }) {
return (
<div>
{todos.map(todo => (
<TodoItem
key={todo.id}
todo={todo}
onToggle={() => onUpdate(todo.id, !todo.completo)} // ❌ Nova função
onDelete={() => onUpdate(todo.id, null)} // ❌ Nova função
/>
))}
</div>
);
}
// TodoItem está usando React.memo mas não adianta
const TodoItem = React.memo(({ todo, onToggle, onDelete }) => {
console.log(`Renderizando ${todo.id}`);
return (
<div>
<span>{todo.texto}</span>
<button onClick={onToggle}>✓</button>
<button onClick={onDelete}>🗑️</button>
</div>
);
});
O que acontece: Console mostra todos os items sendo renderizados, mesmo que só um tenha mudado.
✅ A Solução
/* ================================================
* 💡 EXPLICAÇÃO SIMPLES:
*
* Ao invés de criar funções novas, criamos uma
* "fábrica de funções" memoizada.
*
* A fábrica retorna sempre a MESMA função para
* cada todo.id, evitando re-renders desnecessários.
* ================================================ */
function ListaTodos({ todos, onUpdate }) {
// Fábrica de funções memoizada
const criarHandlerToggle = useMemo(() =>
(todoId, completo) => () => onUpdate(todoId, completo)
, [onUpdate]);
const criarHandlerDelete = useMemo(() =>
(todoId) => () => onUpdate(todoId, null)
, [onUpdate]);
return (
<div>
{todos.map(todo => (
<TodoItem
key={todo.id}
todo={todo}
onToggle={criarHandlerToggle(todo.id, !todo.completo)}
onDelete={criarHandlerDelete(todo.id)}
/>
))}
</div>
);
}
const TodoItem = React.memo(({ todo, onToggle, onDelete }) => {
console.log(`Renderizando ${todo.id}`);
return (
<div>
<span>{todo.texto}</span>
<button onClick={onToggle}>✓</button>
<button onClick={onDelete}>🗑️</button>
</div>
);
});
Agora sim: Console só mostra o item que realmente mudou sendo renderizado.
Quando Isso É Útil
- Listas grandes: Grids com 100+ items
- Componentes React.memo: Que precisam estabilidade de props
- Drag & Drop: Handlers precisam ser estáveis
- Virtualization: react-window, react-virtualized
⚠️ Alternativa Mais Simples
Para a maioria dos casos, useCallback é mais direto:
const handleClick = useCallback((id) => {
onUpdate(id);
}, [onUpdate]);
Use useMemo quando precisar retornar algo além de função.
4. Memoização Inteligente (Só Ativar Quando Necessário)
A Verdade Simples
Não faz sentido memoizar valores de componentes que nem estão visíveis na tela. Isso desperdiça memória.
Vamos Ver O Problema
/* ================================================
* ❌ PROBLEMA: Memoizando mesmo invisível
*
* Component está escondido (display: none) mas
* continua processando dados pesados.
*
* Resultado: Memória e CPU desperdiçados
* ================================================ */
function PainelDados({ dados, isVisivel }) {
const dadosProcessados = useMemo(() => {
console.log('🔄 Processando dados...');
return processamentoMuitoPesado(dados);
}, [dados]); // Roda mesmo quando isVisivel = false
if (!isVisivel) {
return null; // Componente não renderiza mas já processou tudo
}
return <Grafico dados={dadosProcessados} />;
}
O que acontece: Console mostra "🔄 Processando dados..." mesmo quando painel está escondido.
✅ A Solução
/* ================================================
* 💡 EXPLICAÇÃO SIMPLES:
*
* Só processar quando realmente necessário:
* - Componente está visível
* - Dados são grandes (> 100 items)
*
* Se não precisa agora, não processa.
* ================================================ */
function PainelDados({ dados, isVisivel }) {
const dadosGrandes = dados.length > 100;
const deveMemoizar = isVisivel && dadosGrandes;
const dadosProcessados = useMemo(() => {
if (!deveMemoizar) {
return processamentoMuitoPesado(dados);
}
console.log('🔄 Processando dados (memoizado)...');
return processamentoMuitoPesado(dados);
}, deveMemoizar ? [dados] : []); // Array vazio = sempre recalcula
if (!isVisivel) {
return null;
}
return <Grafico dados={dadosProcessados} />;
}
Resultado: Processamento só acontece quando painel fica visível.
Exemplo Mais Prático
// Hook customizado pra memoização inteligente
function useMemoizacaoInteligente(factory, deps, condicoes) {
const { isVisivel = true, tamanhoMinimo = 0 } = condicoes;
const deveMemoizar = isVisivel &&
(deps[0]?.length || 0) >= tamanhoMinimo;
return useMemo(() => {
if (!deveMemoizar) {
return factory();
}
return factory();
}, deveMemoizar ? deps : []);
}
// Uso
function PainelDados({ dados, isVisivel }) {
const dadosProcessados = useMemoizacaoInteligente(
() => processamentoMuitoPesado(dados),
[dados],
{ isVisivel, tamanhoMinimo: 100 }
);
if (!isVisivel) return null;
return <Grafico dados={dadosProcessados} />;
}
Quando Isso É Útil
- Tabs/Modais: Só processar tab ativa
- Scroll infinito: Só processar items visíveis
- Dashboards: Só calcular widgets expandidos
- Mobile: Economizar bateria/CPU
5. Cálculos Em Cadeia (Como Não Reprocessar Tudo)
A Verdade Simples
Se você tem vários cálculos que dependem uns dos outros, pode separar em múltiplos useMemo. Assim, se só uma parte mudar, as outras não recalculam.
Vamos Ver O Problema
/* ================================================
* ❌ PROBLEMA: Um useMemo fazendo tudo
*
* Se chartConfig mudar (coisa barata), TUDO
* é recalculado: filtro caro, agregação cara,
* e formatação barata.
*
* Resultado: Mudou cor do gráfico = recalcula tudo
* ================================================ */
function Dashboard({ dadosRaw, filtros, chartConfig }) {
const dadosProntos = useMemo(() => {
console.log('🔄 TUDO sendo recalculado...');
const filtrados = aplicarFiltros(dadosRaw, filtros); // Caro
const agregados = agregarMetricas(filtrados); // Caro
const formatados = formatarParaGrafico(agregados, chartConfig); // Barato
return formatados;
}, [dadosRaw, filtros, chartConfig]); // ❌ chartConfig força tudo
return <Grafico dados={dadosProntos} />;
}
O que acontece: Mudar cor do gráfico reprocessa milhares de registros.
✅ A Solução
/* ================================================
* 💡 EXPLICAÇÃO SIMPLES:
*
* Separar em camadas:
* 1. Operação mais cara (filtro)
* 2. Operação cara (agregação)
* 3. Operação barata (formatação)
*
* Cada uma só recalcula se SUA dependência mudar.
* ================================================ */
function Dashboard({ dadosRaw, filtros, chartConfig }) {
// Camada 1: Mais cara, depende de dados + filtros
const dadosFiltrados = useMemo(() => {
console.log('🔄 Filtrando dados...');
return aplicarFiltros(dadosRaw, filtros);
}, [dadosRaw, filtros]);
// Camada 2: Cara, depende só dos dados filtrados
const dadosAgregados = useMemo(() => {
console.log('🔄 Agregando métricas...');
return agregarMetricas(dadosFiltrados);
}, [dadosFiltrados]);
// Camada 3: Barata, depende de agregados + config
const dadosFormatados = useMemo(() => {
console.log('🔄 Formatando para gráfico...');
return formatarParaGrafico(dadosAgregados, chartConfig);
}, [dadosAgregados, chartConfig]); // ✅ Só reroda formatação
return <Grafico dados={dadosFormatados} />;
}
Resultado: Mudar cor do gráfico só roda formatação (barata). Filtro e agregação não rodam de novo.
Visualização Do Que Acontece
chartConfig muda:
❌ Antes: Filtro → Agregação → Formatação (TUDO)
✅ Depois: Formatação (SÓ ISSO)
filtros muda:
✅ Filtro → Agregação → Formatação (necessário)
dadosRaw muda:
✅ Filtro → Agregação → Formatação (necessário)
Quando Isso É Útil
- Pipelines de dados: ETL, transformações múltiplas
- Dashboards complexos: Filtros + cálculos + formatação
- Visualizações: Dados → Escalas → Renderização
- Formulários: Validação → Formatação → Submit
6. Operações Assíncronas (Evitar Requisições Duplicadas)
A Verdade Simples
useMemo é síncrono, mas você pode usá-lo pra evitar iniciar a MESMA operação assíncrona múltiplas vezes.
Vamos Ver O Problema
/* ================================================
* ❌ PROBLEMA: Múltiplas requisições iguais
*
* Cada render pode disparar nova requisição,
* mesmo que userId seja o mesmo.
*
* Resultado: API recebe 10 chamadas idênticas
* ================================================ */
function PerfilUsuario({ userId }) {
const [userData, setUserData] = useState(null);
const [loading, setLoading] = useState(false);
useEffect(() => {
setLoading(true);
buscarDadosUsuario(userId).then(dados => {
setUserData(dados);
setLoading(false);
});
}, [userId]); // Re-roda toda vez
if (loading) return <div>Carregando...</div>;
return <div>{userData?.nome}</div>;
}
O que acontece: Durante desenvolvimento (StrictMode), React renderiza 2x, disparando 2 requisições.
✅ A Solução
/* ================================================
* 💡 EXPLICAÇÃO SIMPLES:
*
* Criar hook que:
* 1. Guarda resultado da requisição
* 2. Só faz nova requisição se userId mudar
* 3. Cancela requisição se componente desmontar
* ================================================ */
function useAsyncMemo(asyncFactory, deps) {
const [state, setState] = useState({
data: null,
loading: true,
error: null
});
// Memoiza a última combinação de deps vista
const depsString = deps.map(d => JSON.stringify(d)).join(',');
useEffect(() => {
let cancelado = false;
asyncFactory()
.then(data => {
if (!cancelado) {
setState({ data, loading: false, error: null });
}
})
.catch(error => {
if (!cancelado) {
setState({ data: null, loading: false, error });
}
});
return () => {
cancelado = true; // Cancela se componente desmontar
};
}, [depsString]);
return state;
}
// Uso
function PerfilUsuario({ userId }) {
const { data: userData, loading, error } = useAsyncMemo(
() => buscarDadosUsuario(userId),
[userId]
);
if (loading) return <div>Carregando...</div>;
if (error) return <div>Erro: {error.message}</div>;
return <div>{userData?.nome}</div>;
}
Resultado: Só uma requisição por userId, mesmo com re-renders.
Versão Com Cache
// Hook com cache de 5 minutos
function useAsyncMemoComCache(asyncFactory, deps, tempoCacheMs = 5 * 60 * 1000) {
const cache = useRef(new Map());
const [, forceUpdate] = useState(0);
const cacheKey = JSON.stringify(deps);
const cached = cache.current.get(cacheKey);
const estaNovo = cached &&
(Date.now() - cached.timestamp < tempoCacheMs);
useEffect(() => {
if (estaNovo) return; // Usa cache
let cancelado = false;
cache.current.set(cacheKey, {
data: cached?.data || null,
loading: true,
timestamp: Date.now()
});
forceUpdate(n => n + 1);
asyncFactory()
.then(data => {
if (!cancelado) {
cache.current.set(cacheKey, {
data,
loading: false,
timestamp: Date.now()
});
forceUpdate(n => n + 1);
}
})
.catch(error => {
if (!cancelado) {
cache.current.set(cacheKey, {
data: null,
loading: false,
error,
timestamp: Date.now()
});
forceUpdate(n => n + 1);
}
});
return () => {
cancelado = true;
};
}, [cacheKey, estaNovo]);
return cache.current.get(cacheKey) || {
data: null,
loading: true
};
}
// Uso
function PerfilUsuario({ userId }) {
const { data, loading, error } = useAsyncMemoComCache(
() => buscarDadosUsuario(userId),
[userId],
5 * 60 * 1000 // 5 minutos de cache
);
if (loading) return <div>Carregando...</div>;
if (error) return <div>Erro: {error.message}</div>;
return <div>{data?.nome}</div>;
}
Quando Isso É Útil
- Dados de usuário: Perfil, preferências
- Listas estáticas: Categorias, países
- Configurações: App settings, feature flags
- Buscas: Autocomplete, sugestões
⚠️ Para Produção
Use bibliotecas como:
- React Query: Cache, refetch, mutations
- SWR: Stale-while-revalidate pattern
- Redux Toolkit Query: Integrado com Redux
Você Consegue! 🎉
Agora você sabe a verdade sobre useMemo:
- Array de dependências precisa de valores que React consiga comparar
- Nem tudo deve ser memoizado - só operações caras
- Funções são objetos - precisam de estratégias especiais
- Memoização inteligente economiza recursos
- Cálculos em cadeia evitam reprocessamento total
- Operações assíncronas precisam de cuidados extras
Não é sobre decorar sintaxe. É sobre entender quando e por que usar cada técnica.
Próximo Passo
Abra um projeto seu e:
- Adicione
console.time()em cálculos que você acha caros - Meça se realmente demoram > 1ms
- Aplique
useMemosó nos que valerem a pena - Use React DevTools Profiler pra ver diferença real
Qualquer dúvida, a comunidade React está aí pra ajudar. Todo mundo já se confundiu com isso! 💪
Materiais de Referência
- React Profiler DevTools - Ferramenta oficial para medir performance: https://react.dev/reference/react/Profiler
- Web Performance APIs - Especificação W3C para medição de performance: https://www.w3.org/webperf/
- React Concurrent Features RFC - Documentação técnica oficial sobre features concorrentes: https://github.com/reactjs/rfcs/blob/main/text/0213-suspense-in-react-18.md



