DevPath · Learn to code ESPTEN

Custom hooks and performance

Performance: why React re-renders

Why does a component re-render?

React re-renders a component when:

That third point is key: by default, if a parent re-renders, all of its children re-render in a cascade. Almost always that is cheap and nothing happens. But when a subtree is heavy, it's useful to have tools to avoid unnecessary work.

React.memo: memoizes a component

React.memo wraps a component so that it only re-renders if its props change (shallow comparison). If the parent repaints but the child's props are the same, React reuses the previous render:

const HeavyList = React.memo(function HeavyList({ items }) {
  // expensive render...
  return <ul>{items.map((i) => <li key={i}>{i}</li>)}</ul>;
});

It's only useful if the props stay stable between renders. That's where the next two hooks come in.

useMemo: memoizes a computed value

useMemo remembers the result of a computation and only recomputes it if any of its dependencies change. Ideal for expensive computations:

const sorted = useMemo(() => {
  return [...items].sort((a, b) => a - b); // expensive computation
}, [items]); // only recomputes if 'items' changes

Without useMemo, that sort would run on every render. With it, only when items changes.

useCallback: memoizes a function

On every render, new functions are created. If you pass a function as a prop to a component wrapped in React.memo, that prop "changes" every time and the memoization breaks. useCallback stabilizes the identity of the function:

const handleClick = useCallback(() => {
  setSelection(id);
}, [id]); // same function as long as 'id' doesn't change

useCallback(fn, deps) is equivalent to useMemo(() => fn, deps): one memoizes the function, the other its result.

Mental rule: useMemo for values, useCallback for functions, React.memo for components. All three compare dependencies/props to decide whether they can skip work.

Examples

useMemo avoids recomputing if the dependencies don't change

let computations = 0;
function expensiveCompute(n) { computations++; return n * 2; }
// Render 1
const a = expensiveCompute(21);
// Render 2 with the same input -> with useMemo it would NOT recompute
console.log("result:", a, "| times computed:", computations);
Put this into practice

DevPath is a hands-on course: you read the theory here; in the app you put it into practice with exercises that really run, offline.

Start free in the app →
← Custom hooks: reusable stateful logicWhen (and when not) to optimize →