useMemo and useCallback Are (Mostly) Obsolete

For years, useMemo and useCallback were the default tools for React developers optimizing performance by avoiding repeated computations and function re-creation. With recent React that habit is mostly outdated, and this post covers why the hooks are redundant in most apps and the cases where I still reach for them.

React Fractal compoundnents

The original use case: preventing unnecessary renders

React's reconciliation is efficient, but unnecessary re-renders can still cost performance in complex applications. The two hooks addressed that from different sides:

  • useMemo caches the result of a computation and recalculates only when its dependencies change.
  • useCallback caches a function reference so a child that receives it as a prop does not re-render because the reference changed.

That made sense when React re-rendered more aggressively and components often relied on referential equality to decide whether to update.

Why most components no longer need them

React now does more of the optimization

React 18's concurrent rendering and automatic batching, together with the React Compiler (formerly React Forget), have made React much better at avoiding unnecessary re-renders on its own:

  • The React Compiler memoizes components and values automatically, which removes the need for useMemo and useCallback in most cases.
  • Render scheduling and priority management mean a recomputation that does happen is less likely to block the interface.

Memoization holds memory

useMemo and useCallback keep references alive longer than the code needs them, and when they are applied everywhere, that retained memory plus the dependency comparisons can make performance worse instead of better.

Inline functions are cheap

Many developers add useCallback out of habit, believing it prevents re-renders, when modern JavaScript engines already handle inline functions efficiently. V8 allocates and collects short-lived functions declared inside a component cheaply, and a stable function reference only matters when something downstream actually compares it.

React.memo usually solves the real problem

Wrapping the expensive child in React.memo is often more effective and more concise than memoizing every function the parent passes down:

const ExpensiveComponent = React.memo(({ value }) => {
  console.log("Rendering...");
  return <div>{value}</div>;
});
 
export default function Parent({ count }) {
  return <ExpensiveComponent value={count} />;
}

React.memo re-renders ExpensiveComponent only when value changes, with no useCallback involved.

When I still use useMemo or useCallback

The hooks still have a place in a few specific situations:

  1. Computationally expensive operations. When a component runs an expensive calculation, useMemo keeps it from running on every render.

    const result = useMemo(() => computeExpensiveValue(data), [data]);
  2. Dependencies in custom hooks. When a hook depends on function identity, useCallback keeps the reference stable between renders.

    const fetchData = useCallback(() => fetch(apiUrl), [apiUrl]);
    useEffect(() => { fetchData(); }, [fetchData]);
  3. Context providers. When a provider passes functions through React Context, useCallback keeps consumers from re-rendering on every provider render.

  4. Objects in dependency arrays. When an object is a dependency of useEffect or another hook, useMemo gives it a stable reference.

    const memoizedOptions = useMemo(() => ({ limit: 10 }), []);
  5. A visible UX problem. I start without either hook and add one when I see lag, stuttering animations, or slow renders in a complex component, after profiling shows where the bottleneck is.

In modern React apps I default to leaving useMemo and useCallback out, because overusing them adds complexity and memory and sometimes makes performance worse, and I add them back only where a profile shows a real bottleneck.

Related writing