Hirefullstack – Software Engineering & IT-Beratung aus Berlin
← Back to overview
Core 13 min read

useMemo & useCallback

Keeping results instead of recomputing them

In one sentence

Both keep something between passes: useMemo a result, useCallback a function. Both are optimisations – not standard equipment.

On every draw your component runs top to bottom again. Usually that is cheap. Sometimes it is not – and then you can tell React: “as long as these values do not change, please reuse the earlier result.” That is called MemoisationKeeping a result around instead of computing it again.Along the lines of: “as long as the ingredients don't change, the result stays the same”..

shape.ts
const result = useMemo(() => expensive(a, b), [a, b]);
//                     ▲ function that PRODUCES the value   ▲ what to watch

const fn = useCallback((x) => doSomething(x, a), [a]);
//                     ▲ the function ITSELF is kept
Put another way

A note on the fridge: “as long as a and b stay the same, the answer is this.” Change one of them and it recomputes – otherwise you get the old answer.

Good to know useCallback is only a shorthand

useCallback(fn, deps) is exactly the same as useMemo(() => fn, deps). It exists because keeping functions is such a common need.

There are exactly three good reasons

reasons.txt
1. The computation really is expensive
   (sorting thousands of entries, heavy formatting)

2. The value goes as a prop into a component wrapped in React.memo
   -> without a stable reference memo does nothing

3. The value sits in a dependency list (useEffect and friends)
   -> otherwise the effect runs on every draw

Everything else only makes your code longer and harder to read. Wrapping a + b in useMemo costs more than it saves.

example.tsx
function List({ items, filter, onPick }) {
  // Reason 1: expensive, and the result is displayed
  const visible = useMemo(
    () => items.filter((i) => i.name.includes(filter)).sort(compare),
    [items, filter]
  );

  // Reason 2: goes into a memo component
  const handleClick = useCallback((id) => onPick(id), [onPick]);

  return visible.map((i) => <Row key={i.id} item={i} onClick={handleClick} />);
}

const Row = memo(function Row({ item, onClick }) {
  return <li onClick={() => onClick(item.id)}>{item.name}</li>;
});
Pitfall useCallback on its own does nothing

A stable function only helps if somebody compares it. Hand it to a plain <button onClick={…}> and you have only added work. The button compares nothing.

What React.memo does

React.memo is a wrapper around a component. It says: “if all the props are the same as last time, skip the drawing.” The comparison is Shallow comparisonReact only checks whether the values are the same on the surface – not whether two objects hold the same contents. – it does not look inside objects.

memo.tsx
const Heavy = memo(function Heavy({ data }) { … });

<Heavy data={items} />                   // ✅ same, as long as items is
<Heavy data={items.filter(Boolean)} />   // ❌ a new array every time
<Heavy style={{ color: "red" }} />       // ❌ a new object every time
Tip Restructuring often beats memoising

Passing a child in as children instead of drawing it inside prevents its redraw without any memo at all. And putting state as close as possible to where it is used works just as well. Structure first, memo second.

Reach for it when …
  • +

    Computations that actually show up in the ProfilerA measuring tool in the React DevTools that shows which component took how long.

  • +

    Props for components wrapped in React.memo

  • +

    Values that end up in a dependency list

Skip it when …
  • useMemo around a + b, items.length or a short map

  • Memoising “just in case, for later”

  • useCallback for handlers that go straight to a <button>

In more depth: costs, misunderstandings and the React Compiler optional
Careful Keeping things costs too

Each of these hooks takes memory and compares its list on every pass. For cheap computations that is more expensive than the computation itself.

The most common misunderstanding:useMemo stops the component from redrawing.” It does not. The component still runs top to bottom – only that one computation inside is skipped. Redraws are prevented by React.memo, and only for child components.

Pitfall Nothing with outside effects goes in there

The function inside useMemo runs in the middle of drawing. No fetch and no setState belong there – React may throw it away or repeat it.

Good to know Looking ahead: the React Compiler

The React Compiler (version 1.0 since 2025) inserts this optimisation automatically. Where it runs you hardly ever write useMemo or useCallback by hand. You still need to understand them – existing code is full of them.

Does it stick?

4 questions on this lesson. Wrong answers show up in your stats.

Now write it yourself

Only recompute when you have to

The list is refiltered on every draw – even when only the counter changed. Make it recompute only when the search box changes. The “Computations” line shows you how often it ran.

  • Filtering by an shows only the matching entries
  • Clicking Counter +1 causes no new computation
  • A change in the search box causes one
import { useRef, useState } from "react";

const DATA = ["Banana", "Ananas", "Mandarin", "Cherry"];

export default function App() {
  const [filter, setFilter] = useState("");
  const [count, setCount] = useState(0);
  const computations = useRef(0);

  // TODO: only recompute when filter changes
  computations.current += 1;
  const visible = DATA.filter((d) =>
    d.toLowerCase().includes(filter.toLowerCase())
  );

  return (
    <div style={{ fontFamily: "system-ui", padding: 16 }}>
      <input
        aria-label="filter"
        value={filter}
        onChange={(e) => setFilter(e.target.value)}
        placeholder="filter …"
      />{" "}
      <button onClick={() => setCount((c) => c + 1)}>Counter +1 ({count})</button>
      <p data-testid="computations">Computations: {computations.current}</p>
      <ul>
        {visible.map((d) => (
          <li key={d}>{d}</li>
        ))}
      </ul>
    </div>
  );
}

Hirefullstack

Need React firepower on your team?

We have been building React and Next.js applications for clients across Germany for years – as a single expert, as reinforcement for an existing team, or as a complete Scrum team.

Talk about your project →