useCallback vs useMemo: Quando e Como Usar

Os hooks useCallback e useMemo são ferramentas poderosas para otimização de performance em React, mas é comum haver confusão sobre quando usar cada um.

useCallback - Memorizando Funções

O useCallback memoriza uma função, retornando a mesma referência enquanto suas dependências não mudarem.

useCallback-exemplo.tsxtypescript
import React, { useCallback, useState } from 'react';

interface ChildProps {
  onClick: () => void;
  name: string;
}

const Child = React.memo(({ onClick, name }: ChildProps) => {
  console.log('Child renderizou:', name);
  
  return (
    <button onClick={onClick}>
      Clique em {name}
    </button>
  );
});

function Parent() {
  const [count, setCount] = useState(0);
  const [name, setName] = useState('João');

  // ❌ Sem useCallback - nova função a cada render
  const handleClickBad = () => {
    console.log('Clicou!');
  };

  // ✅ Com useCallback - mesma função enquanto deps não mudarem
  const handleClickGood = useCallback(() => {
    console.log('Clicou!');
  }, []); // Sem dependências

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>
        Incrementar
      </button>
      
      <Child onClick={handleClickBad} name="Bad" />
      <Child onClick={handleClickGood} name="Good" />
    </div>
  );
}
Dica
No exemplo acima, o componente Child com handleClickBad será re-renderizado toda vez que Parent renderizar, mesmo sendo envolvido por React.memo.

useMemo - Memorizando Valores

O useMemo memoriza o resultado de um cálculo, evitando recalcular valores caros desnecessariamente.

useMemo-exemplo.tsxtypescript
import React, { useMemo, useState } from 'react';

function ExpensiveComponent() {
  const [count, setCount] = useState(0);
  const [items, setItems] = useState([1, 2, 3, 4, 5]);

  // ❌ Sem useMemo - recalcula a cada render
  const expensiveValueBad = items.reduce((acc, item) => {
    // Simula cálculo pesado
    for (let i = 0; i < 1000000; i++) {}
    return acc + item;
  }, 0);

  // ✅ Com useMemo - só recalcula quando items mudar
  const expensiveValueGood = useMemo(() => {
    console.log('Calculando valor caro...');
    return items.reduce((acc, item) => {
      // Simula cálculo pesado
      for (let i = 0; i < 1000000; i++) {}
      return acc + item;
    }, 0);
  }, [items]);

  return (
    <div>
      <p>Count: {count}</p>
      <p>Expensive Value: {expensiveValueGood}</p>
      
      <button onClick={() => setCount(count + 1)}>
        Incrementar Count
      </button>
      
      <button onClick={() => setItems([...items, items.length + 1])}>
        Adicionar Item
      </button>
    </div>
  );
}

Casos Práticos de Uso

useCallback - Quando Usar

useCallback-casos.tsxtypescript
// 1. Passando funções para componentes memorizados
const MemoizedChild = React.memo(({ onSubmit }) => {
  return <form onSubmit={onSubmit}>...</form>;
});

function Parent() {
  const handleSubmit = useCallback((data) => {
    // Lógica de submit
  }, []);

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

// 2. Dependência de outros hooks
function useCustomHook(callback) {
  useEffect(() => {
    // callback é dependência
    callback();
  }, [callback]);
}

function Component() {
  const stableCallback = useCallback(() => {
    // Lógica
  }, []);

  useCustomHook(stableCallback);
}

useMemo - Quando Usar

useMemo-casos.tsxtypescript
// 1. Cálculos caros
function DataProcessor({ data }) {
  const processedData = useMemo(() => {
    return data
      .filter(item => item.active)
      .map(item => ({
        ...item,
        computed: heavyComputation(item)
      }))
      .sort((a, b) => a.priority - b.priority);
  }, [data]);

  return <DataTable data={processedData} />;
}

// 2. Objetos como dependências
function Component({ userId }) {
  const userConfig = useMemo(() => ({
    id: userId,
    settings: getDefaultSettings(),
    permissions: calculatePermissions(userId)
  }), [userId]);

  return <UserProfile config={userConfig} />;
}

// 3. Referências estáveis para Context
function Provider({ children }) {
  const [state, setState] = useState(initialState);
  
  const contextValue = useMemo(() => ({
    state,
    actions: {
      update: (data) => setState(data),
      reset: () => setState(initialState)
    }
  }), [state]);

  return (
    <Context.Provider value={contextValue}>
      {children}
    </Context.Provider>
  );
}

Quando NÃO Usar

quando-nao-usar.tsxtypescript
// ❌ Não use para valores primitivos simples
const Component = () => {
  const [name, setName] = useState('');
  
  // Desnecessário - string simples
  const uppercaseName = useMemo(() => name.toUpperCase(), [name]);
  
  // ✅ Melhor assim
  const uppercaseNameSimple = name.toUpperCase();
};

// ❌ Não use useCallback sem React.memo
const Parent = () => {
  const [count, setCount] = useState(0);
  
  // Desnecessário se Child não é memorizado
  const handleClick = useCallback(() => {
    console.log('clicked');
  }, []);
  
  return <Child onClick={handleClick} />; // Child não é React.memo
};

// ❌ Dependências que sempre mudam
const Component = () => {
  const [items, setItems] = useState([]);
  
  // Inútil - objeto sempre novo
  const config = useMemo(() => ({
    timestamp: Date.now(), // Sempre diferente!
    items: items
  }), [items]);
};

Dicas de Performance

  1. Meça antes de otimizar: Use React DevTools Profiler
  2. Combine com React.memo: useCallback é mais útil com componentes memorizados
  3. Cuidado com dependências: Arrays e objetos sempre "mudam"
  4. Considere o custo: Memorização tem overhead próprio
profiling-exemplo.tsxtypescript
// Use React DevTools para identificar re-renders desnecessários
function ProfiledComponent() {
  // Envolva componentes suspeitos com Profiler
  return (
    <Profiler id="ExpensiveComponent" onRender={onRenderCallback}>
      <ExpensiveComponent />
    </Profiler>
  );
}

function onRenderCallback(id, phase, actualDuration) {
  console.log('Component:', id);
  console.log('Phase:', phase);
  console.log('Duration:', actualDuration);
}

Conclusão

useCallback e useMemo são ferramentas valiosas, mas devem ser usados com parcimônia:

  • useCallback: Para estabilizar referências de funções
  • useMemo: Para evitar cálculos caros desnecessários
  • Ambos: Só quando há benefício real de performance