useMemo, useCallback e useRef
"Usa sempre que puder" é o conselho errado — cada um resolve um problema bem específico, e usar sem necessidade também tem custo.
É comum ouvir "usa useMemo e useCallback em tudo, por garantia". Isso é conselho ruim: os dois têm um custo real (guardar valor em memória, comparar o array de dependências a cada render), e cada um existe pra resolver um problema específico — não pra "otimizar" de forma genérica.
useMemo — cachear um valor calculado caro
Sem useMemo, uma computação cara dentro do componente roda de novo a cada render — inclusive quando o render foi causado por algo que não tem nada a ver com aquele cálculo.
function ListaDeProdutos({ produtos, termoBusca }: Props) {
const [tema, setTema] = useState("light"); // mudar isso recalcula a lista inteira também
const filtrados = produtos
.filter((p) => p.nome.includes(termoBusca))
.sort((a, b) => a.preco - b.preco); // caro se a lista for grande
return <ul>{filtrados.map((p) => <li key={p.id}>{p.nome}</li>)}</ul>;
}function ListaDeProdutos({ produtos, termoBusca }: Props) {
const [tema, setTema] = useState("light");
const filtrados = useMemo(
() => produtos.filter((p) => p.nome.includes(termoBusca)).sort((a, b) => a.preco - b.preco),
[produtos, termoBusca]
);
return <ul>{filtrados.map((p) => <li key={p.id}>{p.nome}</li>)}</ul>;
}useCallback — cachear a identidade de uma função
useCallback não cacheia um valor calculado — ele cacheia a referência de uma função, pra ela não virar uma função "nova" a cada render. Isso importa quando essa função é prop de um componente memoizado com React.memo, ou dependência de outro hook.
const BotaoCurtir = memo(function BotaoCurtir({ onClick }: { onClick: () => void }) {
return <button onClick={onClick}>❤ Curtir</button>;
});
function Post({ id }: { id: string }) {
const [tema, setTema] = useState("light");
const curtir = () => enviarLike(id); // nova função a cada render de Post
return <BotaoCurtir onClick={curtir} />; // memo não evita re-render: a prop "onClick" sempre muda
}function Post({ id }: { id: string }) {
const [tema, setTema] = useState("light");
const curtir = useCallback(() => enviarLike(id), [id]);
return <BotaoCurtir onClick={curtir} />; // agora o memo funciona de verdade
}useRef — valor mutável que não dispara render
useState guarda valor e causa re-render quando muda. useRef guarda valor e não causa re-render — útil pra coisas que o componente precisa lembrar entre renders, mas que não fazem parte do que é exibido na tela: o ID de um setTimeout, o valor anterior de uma prop, uma referência a um elemento do DOM.
function CampoComAutoSalvar({ valor, onSalvar }: Props) {
const timeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
function handleChange(novoValor: string) {
if (timeoutRef.current) clearTimeout(timeoutRef.current);
timeoutRef.current = setTimeout(() => onSalvar(novoValor), 800);
}
return <input defaultValue={valor} onChange={(e) => handleChange(e.target.value)} />;
}O custo de otimizar sem necessidade
useMemo e useCallback não são "grátis": React precisa guardar o valor anterior e comparar o array de dependências a cada render. Pra um cálculo barato (somar dois números, formatar uma string curta), esse custo de comparação pode ser maior que o custo de simplesmente recalcular. A regra prática: meça antes de otimizar (React DevTools Profiler mostra onde o tempo realmente vai) — não adicione os dois hooks por reflexo em todo componente que você escrever.
Exercício
No componente abaixo, ordenarPedidos é uma função cara e ListaPedidos é memoizado com React.memo. Toda vez que o componente pai re-renderiza (por qualquer motivo), a lista inteira é reordenada e o memo do filho não evita re-render nenhum. Reescreva usando useMemo e useCallback onde fizer sentido.