useMemo and useCallback Are (Mostly) Obsolete

January 17, 2025

For years, useMemo and useCallback have been the go-to tools for React developers looking to optimize performance by avoiding unnecessary computations and function re-creations. With recent React, that habit is mostly outdated. Here's why they're redundant in most apps, and when I still reach for them.

React Fractal compoundnents

The original use case: preventing unnecessary renders

React's reconciliation algorithm is quite efficient, but unnecessary re-renders can still impact performance, especially in complex applications. The idea behind useMemo and useCallback was simple:

  • useMemo: Caches the result of a computation, preventing recalculations unless dependencies change.
  • useCallback: Caches a function reference to prevent child components from unnecessarily re-rendering when passed as props.

This made sense in the era when React re-renders were more aggressive and when components frequently relied on referential equality to determine updates.

Why they're no longer necessary in most cases

1. Modern React optimizations make them redundant

With React 18's Concurrent Mode, automatic batching, and improvements in React Compiler optimizations (like RSC and React Forget), React has become significantly smarter about avoiding unnecessary re-renders. Components now update more efficiently, reducing the need for manual memoization.

  • Automatic memoization in React Compiler: The React team is working on a compiler that will optimize component re-renders automatically, removing the need for useMemo and useCallback in most cases.
  • Rendering scheduling and priority management: React now schedules renders more intelligently, making unnecessary recomputations a smaller issue than before.

2. Garbage collection and memory pressure

One of the problems with useMemo and useCallback is that they hold onto references longer than necessary, leading to unnecessary memory retention. In some cases, their overuse can actually harm performance rather than help. This is a common issue in software engineering.

3. Inline functions are cheap in modern JavaScript engines

Many developers use useCallback out of habit, believing it prevents unnecessary re-renders. But in most cases, inline functions are already optimized by modern JavaScript engines, making useCallback redundant.

  • Modern V8 optimizations ensure that functions declared inline inside a component are efficiently garbage-collected and reallocated.
  • Referential equality checks are not always meaningful in React reconciliation, as components don't always rely on the same function reference.

4. React.memo is usually a better approach

Instead of manually memoizing functions with useCallback, simply wrapping a component with React.memo is often the more effective and concise solution.

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

React.memo ensures that ExpensiveComponent only re-renders when value actually changes—without needing useCallback.

When should you still use useMemo or useCallback?

While largely unnecessary, useMemo and useCallback still have valid use cases in complex applications:

  1. Computationally expensive operations

    • If a component performs expensive calculations, memoizing the result with useMemo prevents unnecessary recomputation.
    const result = useMemo(() => computeExpensiveValue(data), [data]);
  2. Memoizing dependencies in custom hooks

    • If a custom hook depends on function identity, useCallback ensures the function reference doesn't change between renders.
    const fetchData = useCallback(() => fetch(apiUrl), [apiUrl]);
    useEffect(() => { fetchData(); }, [fetchData]);
  3. Preventing unnecessary renders in context providers

    • When passing functions down through React Context, useCallback can help prevent unnecessary re-renders of consumers.
  4. Avoiding unnecessary object creation in dependency arrays

    • When passing objects as dependencies in hooks like useEffect, useMemo ensures stable object references.
    const memoizedOptions = useMemo(() => ({ limit: 10 }), []);
  5. When you notice a UX problem

    • Start without useMemo and useCallback. Add them when you see lag, stuttering animations, or delays in rendering complex components.
    • Instead of preemptively optimizing, profile the app and add these hooks when a real bottleneck shows up.

In modern React apps, default to not using useMemo and useCallback unless you truly need them. Overusing them adds complexity, extra memory, and sometimes worse performance.

Trust React's rendering optimizations and use these hooks only when profiling reveals a bottleneck. Manually memoizing everything is fading—React itself is getting good enough to handle performance for us. That's useful context for technical leadership on React projects.

Related writing