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.

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:
useMemocaches the result of a computation and recalculates only when its dependencies change.useCallbackcaches 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
useMemoanduseCallbackin 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:
-
Computationally expensive operations. When a component runs an expensive calculation,
useMemokeeps it from running on every render.const result = useMemo(() => computeExpensiveValue(data), [data]); -
Dependencies in custom hooks. When a hook depends on function identity,
useCallbackkeeps the reference stable between renders.const fetchData = useCallback(() => fetch(apiUrl), [apiUrl]); useEffect(() => { fetchData(); }, [fetchData]); -
Context providers. When a provider passes functions through React Context,
useCallbackkeeps consumers from re-rendering on every provider render. -
Objects in dependency arrays. When an object is a dependency of
useEffector another hook,useMemogives it a stable reference.const memoizedOptions = useMemo(() => ({ limit: 10 }), []); -
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.