When useMemo and useCallback Actually Help

This guide is part of React Rendering Performance, within Framework Performance. useMemo caches the result of a calculation between renders; useCallback caches a function's identity. Both are widely used and widely misused. Wrapped around cheap calculations or callbacks passed to plain DOM elements, they add code, comparison overhead and memory without saving anything. Used in the right places, they prevent expensive recalculation and keep memoized children from re-rendering.

There are exactly three situations where they help: the calculation itself is expensive; the value or function is passed to a child wrapped in React.memo (so a stable identity lets the child skip rendering); or the value is a dependency of an effect or another hook, where a new identity would re-run it. Outside these cases, they are noise. The React Compiler automates most of this, but understanding the rules still matters for code the compiler cannot optimise and for reading profiles.

Should I wrap this in useMemo or useCallback? Decision sequence for whether a value or function benefits from memoization. Should I wrap this in useMemo or useCallback? Is the calculation measurably expensive (over ~1ms)? useMemo yes no Is it passed to a React.memo child? useMemo or useCallback to keep identity stable yes no Is it an effect or hook dependency? Memoize to avoid re-running the effect yes no Do not memoize — it only adds overhead

Rapid Diagnosis

  • Profile first: use the React Profiler to find expensive components and why they re-render.
  • Time suspicious calculations: wrap them with console.time in a production-like build.
  • Check whether children are memoized: stable props only matter for memoized children.
  • Look for effects re-running on every render due to object or function dependencies.

Root Cause Analysis

1. Expensive derived data recomputed every render. Sorting, filtering or aggregating large arrays.

2. Memoized children receiving new props. Inline objects and callbacks defeat React.memo.

3. Effects with unstable dependencies. Re-subscribing or refetching on every render.

4. Over-memoization. Memo wrappers everywhere make code harder to read with no gain.

Step-by-Step Resolution

1. Memoize expensive derived data

javascript
function OrdersTable({ orders, filter }) {
  const visible = useMemo(
    () => orders.filter((o) => o.status === filter).sort((a, b) => b.total - a.total),
    [orders, filter],
  );
  return <Table rows={visible} />;
}
// trade-off: useMemo keeps the previous result in memory and compares deps each
// render; worthwhile here because filtering and sorting thousands of rows is slow.

2. Stabilise props for memoized children

javascript
const Row = React.memo(function Row({ order, onSelect }) { /* ... */ });
function OrdersTable({ orders }) {
  const [selected, setSelected] = useState(null);
  const onSelect = useCallback((id) => setSelected(id), []);   // stable identity
  return orders.map((o) => <Row key={o.id} order={o} onSelect={onSelect} />);
}
// trade-off: without React.memo on Row, useCallback here would do nothing useful.

3. Stabilise effect dependencies

Memoize objects or functions used in effect dependency arrays when a new identity would re-run the effect (re-subscribing, refetching), or move the object creation inside the effect.

4. Remove memoization that does nothing

Delete useMemo around trivial expressions and useCallback for handlers passed to DOM elements (<button onClick>), which do not benefit from stable identities.

Memoization cases Common memoization uses and whether they help performance. Memoization cases Case Helps? Why Filtering/sorting 5,000 items yes avoids expensive recomputation Callback to React.memo child yes child can skip rendering Object in effect deps yes effect does not re-run Callback to a button element no DOM elements do not compare props a + b, string concatenation no cheaper than the memo bookkeeping

Verification

Re-profile the interaction: memoized children should show as not rendered, and expensive calculations should disappear from flame charts on unrelated updates. Check that effects no longer re-run unnecessarily (log or count). Confirm in the Performance panel that interaction time decreased.

Worked Example: An Analytics Dashboard

An analytics dashboard recalculated aggregates for six charts from a 20,000-row dataset on every render, including renders caused by hovering over chart tooltips. Each hover cost 120ms. Wrapping the aggregation in useMemo keyed on the dataset and date range removed the recalculation from hover renders; memoizing the chart components and stabilising their onHover callbacks with useCallback stopped five of six charts re-rendering on each hover. Hover interactions dropped to 8ms. A later code review removed 40 other useMemo and useCallback calls around trivial values, which had no measurable effect on performance and made the code easier to read.

How the React Compiler Changes This

The React Compiler memoizes automatically: it caches JSX elements, computed values and callbacks based on their inputs, at a finer granularity than manual hooks. With the compiler enabled, most manual useMemo, useCallback and React.memo calls become unnecessary (they remain harmless). Manual memoization is still relevant for code the compiler skips (components that break the Rules of React), for very expensive calculations where you want explicit control, and in codebases not yet using the compiler.

Tooltip hover cost on an analytics dashboard Bar chart comparing interaction time for a tooltip hover before and after targeted memoization. Tooltip hover cost on an analytics dashboard No memoization 120ms useMemo on aggregates 40ms + memoized charts and stable callbacks 8ms

Measuring Before and After

Treat memoization like any other optimisation: measure first, change one thing, measure again. In the React Profiler, record the interaction and note the component's render time and how often it renders. Add the memoization, record again, and confirm that the component now shows as not rendered (or renders faster). If nothing changes, remove the memoization — it was not addressing the actual cost. For calculations, wrap them temporarily with performance.now() timing in a production build on a throttled device; a calculation that takes 0.1ms does not need useMemo, however large the array looks in the code.

Common Mistakes

  • Memoizing without React.memo on the child. Stable props are pointless if the child always re-renders.
  • Wrong dependency arrays. Missing dependencies cause stale values; extra ones cause recomputation.
  • Memoizing cheap work. Adds overhead and complexity.
  • Using useMemo for semantic guarantees. React may discard cached values; do not rely on it for correctness.

Edge Cases

Context values. Memoize context provider values so consumers do not re-render on every provider render.

Custom hooks. Return stable objects and functions from custom hooks that are used in dependencies elsewhere.

Lists. Memoizing list items helps most when the list re-renders often with mostly unchanged items.

Large objects. Memoization keeps old results alive until dependencies change; be mindful of memory for very large data.

FAQ

Does useCallback make functions faster?

No. It only keeps the same function identity between renders, which matters for memoized children and dependency arrays.

Is useMemo free?

No. It compares dependencies every render and stores results. For cheap calculations, the overhead can exceed the savings.

How expensive must a calculation be to memoize?

A rough threshold is around 1ms or more per render on target devices, or calculations on large collections. Measure with throttling.

Should I memoize context values?

Yes. A provider value created inline changes identity every render and re-renders all consumers.

Do I still need these hooks with the React Compiler?

Mostly not. The compiler memoizes automatically; keep manual hooks only where the compiler cannot optimise or explicit control is needed.

Why does my memoized child still re-render?

One of its props changes identity each render (an inline object, array or callback), or it consumes a context that changes.

Can memoization cause bugs?

Incorrect dependency arrays can produce stale values. The react-hooks/exhaustive-deps lint rule catches most of these mistakes.

Should I memoize event handlers on DOM elements?

No. DOM elements do not skip work based on prop identity, so stable handlers bring no benefit there.

Does useMemo help with expensive child components?

Indirectly. Memoizing a JSX element with useMemo (or wrapping the child in React.memo) lets React skip re-rendering that subtree when its inputs are unchanged.

How do I memoize values in custom hooks?

Use useMemo and useCallback for values and functions the hook returns, so consumers can use them in dependency arrays and memoized children without causing extra work.

Is it bad to memoize everything just in case?

It is rarely harmful to performance in a big way, but it adds code, memory and dependency arrays to maintain. Targeted memoization, or the compiler, gives the benefit with less noise.