Abre qualquer PR de React hoje em dia e tem grande chance de encontrar um useMemo envolvendo uma soma de dois números ou um useCallback em uma função que só é usada dentro do próprio componente. Isso não é otimização. É ruído.
Índice
- O reflexo que virou hábito
- O que useMemo realmente faz
- Quando useMemo vale a pena
- useCallback e a armadilha das dependências
- Casos reais onde faz diferença
- Erros comuns
- Checklist antes de usar
- FAQ
- Próximos passos
O reflexo que virou hábito
Em algum momento entre o React 16 e o 18, memoização virou sinônimo de "código profissional". Todo componente ganhou seu useMemo, toda função virou useCallback. O problema é que memoização tem custo. Ela guarda um valor em memória, compara dependências a cada render e só então decide se recalcula ou não. Se o cálculo que você está evitando é mais barato que essa comparação, você piorou a performance para parecer que melhorou.
Por que isso importa? Porque em produção ninguém vai medir "quantos hooks de otimização o componente tem". Vão medir se a tela trava ao digitar num input ou não.
O que useMemo realmente faz
useMemo recebe uma função e um array de dependências. Ele executa a função uma vez, guarda o resultado e só executa de novo se alguma dependência mudar entre renders.
// ❌ Memoizando algo que já é barato
const total = useMemo(() => a + b, [a, b]);
// ✓ Isso já resolve, sem overhead nenhum
const total = a + b;
A conta a + b custa menos que o processo de comparar [a, b] com o array anterior. O React ainda precisa alocar espaço para guardar o resultado memoizado e rodar essa comparação a cada render — isso tem um custo, pequeno, mas real. Para operações triviais, você está pagando um pedágio para evitar um cálculo que já era de graça.
Quando useMemo vale a pena
Existem dois cenários onde useMemo realmente entrega valor:
- Cálculo pesado de verdade — filtragem, ordenação ou transformação de listas grandes, cálculos matemáticos complexos, parsing de dados.
- Preservar identidade de referência — quando o valor memoizado é passado como dependência de outro hook ou como prop para um componente memoizado com
React.memo.
// ✓ Filtrar uma lista de milhares de itens a cada render é caro
const filteredUsers = useMemo(() => {
return users.filter(user => user.name.includes(searchTerm));
}, [users, searchTerm]);
💡 Dica: antes de memoizar, pergunte: "esse cálculo aparece no profiler do React DevTools como algo relevante?" Se a resposta é não, não memoize. Meça primeiro, otimize depois.
useCallback e a armadilha das dependências
useCallback não evita a criação da função — ela é criada em todo render de qualquer forma, é como o JavaScript funciona. O que ele evita é que uma nova referência dessa função seja passada adiante, quebrando a memoização de um componente filho.
// ❌ Isso não protege nada se o filho não for memoizado
const handleClick = useCallback(() => {
setCount(c => c + 1);
}, []);
return <button onClick={handleClick}>Incrementar</button>;
Aqui, <button> é um elemento nativo do DOM. Ele não tem React.memo, não se importa com identidade de referência. O useCallback nesse caso não faz absolutamente nada além de adicionar complexidade.
// ✓ Agora sim: ItemCard é memoizado e depende da referência estável
const ItemCard = React.memo(function ItemCard({ onSelect, item }) {
return <div onClick={() => onSelect(item.id)}>{item.name}</div>;
});
function List({ items }) {
const handleSelect = useCallback((id) => {
console.log('selecionado', id);
}, []);
return items.map(item => (
<ItemCard key={item.id} item={item} onSelect={handleSelect} />
));
}
Isso apareceu num PR meu em 2023 e o revisor só comentou: "por quê?". Eu tinha useCallback em uma função passada para uma <div> comum. Boa pergunta, e eu não tinha resposta.
Casos reais onde faz diferença
- Listas longas com React.memo — uma tabela com centenas de linhas, cada linha memoizada, recebendo callbacks do componente pai.
- Dependências de outros hooks — quando o resultado de um
useMemoentra no array de dependências de umuseEffect, evitando loops de re-execução. - Bibliotecas de gráficos e canvas — recalcular pontos de um gráfico a cada render trava a interface perceptivelmente.
- Contextos com múltiplos consumidores — memoizar o valor passado a um
Context.Providerevita re-render em cascata de toda a árvore que consome aquele contexto.
// ✓ Contexto: sem isso, todo consumidor re-renderiza a cada render do Provider
const value = useMemo(() => ({ user, theme, setTheme }), [user, theme]);
return <AppContext.Provider value={value}>{children}</AppContext.Provider>;
Erros comuns
Memoizar sem medir. O dev vê o componente re-renderizando no DevTools, entra em pânico e joga useMemo em tudo, sem confirmar se aquele re-render estava causando algum problema real.
Esquecer que objetos e arrays literais quebram a memoização.
// ❌ Isso cria um objeto novo a cada render, mesmo com useCallback no filho
<ItemCard config={{ theme: 'dark' }} onSelect={handleSelect} />
// ✓ Memoize também o objeto, ou tire ele do JSX
const config = useMemo(() => ({ theme: 'dark' }), []);
Dependências incompletas. ESLint com react-hooks/exhaustive-deps existe por um motivo. Ignorar o aviso e silenciar com um comentário costuma virar bug de estado desatualizado (stale closure) três sprints depois, quando ninguém mais lembra por que aquele array estava incompleto.
⚠️ Atenção: nunca desative
exhaustive-depssó para o warning sumir. Se a dependência realmente não deveria disparar o recálculo, resolva isso movendo a lógica para dentro do efeito ou usando um ref — não escondendo o aviso.
Checklist antes de usar
| Pergunta | Se a resposta for sim |
|---|---|
| O cálculo aparece como lento no profiler? | Considere useMemo |
| O valor vai para um componente memoizado ou dependência de hook? | Considere useMemo/useCallback |
| É uma operação O(1) simples (soma, concatenação)? | Não memoize |
A função vai para um elemento nativo (button, div)? | Não use useCallback |
| Você não mediu nada, só "acha" que vai ajudar? | Não memoize ainda |
FAQ
useMemo e useCallback evitam re-render do componente atual?
Não. Eles não impedem que o próprio componente re-renderize. Eles evitam recalcular um valor ou recriar uma referência de função, o que só importa se esse valor for usado por algo que se importa com identidade de referência, como React.memo ou o array de dependências de outro hook.
Vale usar useMemo em todo componente "por garantia"?
Não. Cada useMemo adiciona uma comparação de dependências e ocupa memória. Em componentes pequenos e cálculos triviais, isso é overhead puro, sem nenhum ganho perceptível.
React Compiler não resolve isso automaticamente?
O React Compiler, quando habilitado no projeto, memoiza automaticamente boa parte desses casos em tempo de build. Isso reduz a necessidade de useMemo/useCallback manuais, mas entender o que ele está fazing por baixo dos panos ainda ajuda a debugar performance quando algo foge do esperado.
Como eu meço se realmente preciso memoizar? Use o profiler do React DevTools, ative "Highlight updates when components render" e observe quais componentes re-renderizam sem necessidade. Meça antes e depois de aplicar a memoização — se a diferença não aparece no gráfico, provavelmente não valia a pena.
useCallback resolve problema de closure desatualizada?
Não resolve sozinho. Se as dependências estiverem corretas, o useCallback até ajuda a expor o problema mais cedo, porque o ESLint vai avisar quando uma variável usada dentro da função não está na lista de dependências.
Próximos passos
- Abra o React DevTools Profiler no seu projeto atual e procure componentes que re-renderizam sem necessidade antes de adicionar qualquer memoização.
- Remova
useMemo/useCallbackde cálculos triviais que você encontrar em código existente — teste se a performance muda (não vai mudar). - Se seu projeto já usa React 19 ou está migrando para lá, avalie o React Compiler antes de espalhar memoização manual por toda a base de código.