Chapter 26 β€” useMemo vs useCallback

πŸ“– Definition

  • useMemo(fn, deps) β€” memoizes a value (the result of fn()).
  • useCallback(fn, deps) β€” memoizes a function reference itself.

useCallback(fn, deps) is equivalent to useMemo(() => fn, deps).

πŸ” Explanation

Both hooks help avoid unnecessary work when components re-render:

  • useMemo β€” skip an expensive computation if its inputs haven't changed.
  • useCallback β€” keep a stable function identity so child components wrapped in React.memo don't re-render unnecessarily.

⚠️ Don't over-memoize. Memoization has its own cost. Use only when there's a measurable performance issue or a memoized child relies on stable references.

πŸ’» Code Example β€” useMemo for Expensive Computation

function ProductList({ products, query }) {
  // Without useMemo, filter runs on every render β€” even when typing in an unrelated input.
  const filtered = useMemo(
    () => products.filter(p =>
      p.name.toLowerCase().includes(query.toLowerCase())
    ),
    [products, query]
  );

  return (
    <ul>
      {filtered.map(p => <li key={p.id}>{p.name}</li>)}
    </ul>
  );
}

πŸ’» Code Example β€” useCallback with React.memo

const Child = React.memo(function Child({ onClick }) {
  console.log("Child rendered");
  return <button onClick={onClick}>Click</button>;
});

function Parent() {
  const [count, setCount] = useState(0);

  // ❌ Without useCallback: new handler reference each render β†’ Child re-renders
  // const handleClick = () => console.log("clicked");

  // βœ… With useCallback: stable reference β†’ Child stays memoized
  const handleClick = useCallback(() => console.log("clicked"), []);

  return (
    <>
      <button onClick={() => setCount(count + 1)}>Re-render ({count})</button>
      <Child onClick={handleClick} />
    </>
  );
}

πŸ’» Code Example β€” When NOT to Use useMemo

// ❌ Useless β€” wrapping a simple expression adds overhead
const total = useMemo(() => a + b, [a, b]);

// βœ… Just compute it
const total = a + b;

Memoize only when:

  1. The computation is expensive (heavy filter, sort, parse).
  2. The result is passed to a memoized child as a prop.
  3. The result is used as a dependency in another hook.

πŸ’» Code Example β€” Memoizing a Derived Object

function Map({ lat, lng }) {
  // Object literal is recreated each render β†’ effect re-runs forever.
  const coords = useMemo(() => ({ lat, lng }), [lat, lng]);

  useEffect(() => {
    initMap(coords);
  }, [coords]);
}

πŸ’» Code Example β€” useCallback as Dependency

function Searchbar({ onSearch }) {
  const [query, setQuery] = useState("");

  // onSearch is a prop function β€” wrap it in callback so the effect is stable
  useEffect(() => {
    const id = setTimeout(() => onSearch(query), 400);
    return () => clearTimeout(id);
  }, [query, onSearch]);

  // Parent should wrap onSearch with useCallback to keep this effect stable.
}

πŸ’» Code Example β€” Heavy Sort

function LeaderBoard({ players, sortBy }) {
  const sorted = useMemo(() => {
    console.log("Sorting…");
    return [...players].sort((a, b) => b[sortBy] - a[sortBy]);
  }, [players, sortBy]);

  return <Table rows={sorted} />;
}

πŸ’» Code Example β€” Combined Use

const FilteredList = React.memo(function FilteredList({ items, predicate }) {
  console.log("FilteredList render");
  const filtered = useMemo(() => items.filter(predicate), [items, predicate]);
  return <ul>{filtered.map(i => <li key={i.id}>{i.name}</li>)}</ul>;
});

function App() {
  const [items] = useState([
    { id: 1, name: "Apple",  active: true  },
    { id: 2, name: "Banana", active: false },
    { id: 3, name: "Cherry", active: true  },
  ]);

  const predicate = useCallback((i) => i.active, []);

  return <FilteredList items={items} predicate={predicate} />;
}

Without useCallback, every render of App would create a fresh predicate, defeating React.memo.

🌍 Real-World Impact

  • Dashboards with heavy aggregations β†’ wrap in useMemo.
  • Large lists with memoized item components β†’ wrap row props in useCallback.
  • Be pragmatic: profile first with React DevTools' Profiler before sprinkling memoization.

🎯 Likely Interview Questions

  1. Difference between useMemo and useCallback?
  2. When should you use them?
  3. Why might React.memo not work as expected? β€” Because a parent passes a new object/function reference on every render. Stabilize with useMemo/useCallback.
  4. Are they free? β€” No, both store the previous deps and result, and run shallow comparisons each render. Overusing them hurts performance.

← useEffect | Index | Next: useRef β†’