Updated June 2026. Tested on React 19 and modern JavaScript. Part of the Techalyst React series.
React re-renders often, and that is fine, because a render is usually cheap. Memoisation is how you skip the renders and the calculations that are not cheap. The trouble is that the three tools, useMemo, useCallback and React.memo, get sprinkled everywhere out of habit, where they add complexity and buy nothing. They only pay off when you understand how they work together and where they actually help.
useMemo caches a value
useMemo remembers the result of a calculation and only re-runs it when its dependencies change. It is for genuinely expensive work, sorting a big list, a heavy transform, not for cheap arithmetic:
const sortedItems = useMemo(() => {
return [...items].sort((a, b) => a.price - b.price);
}, [items]);
As long as items does not change, the sort does not run again on re-render. Note the spread before sorting, so we do not mutate state, the same rule as everywhere in React.
useCallback caches a function
Every render creates new function objects. Usually that does not matter. It starts to matter when a function is a dependency of an effect, or is passed to a child that compares its props. useCallback keeps the same function identity between renders until its dependencies change:
const handleSelect = useCallback((id) => {
setSelected(id);
}, []);
It is the function version of useMemo, in fact useCallback(fn, deps) is exactly useMemo(() => fn, deps). Reach for it only when a stable function identity actually matters, not on every handler you write.
React.memo skips a re-render
By default a child re-renders whenever its parent does, even if its props are identical. React.memo wraps a component so it skips the re-render when its props have not changed, by a shallow comparison:
const ProductRow = React.memo(function ProductRow({ product }) {
return <div>{product.name}</div>;
});
Now ProductRow only re-renders when its product prop is a different value, which is what you want for an expensive row in a long list.
How the three work together
Here is the part that makes or breaks it. React.memo compares props by reference, so it only helps if the props you pass are stable. If a parent passes a fresh function or a freshly built object every render, the memoised child sees new props every time and re-renders anyway, and your React.memo did nothing.
That is the whole reason useCallback and useMemo exist alongside it. You wrap the function in useCallback and the object in useMemo so their identity is stable, which lets the memoised child actually skip. The three are a set: React.memo on the child, stable props from the parent.
When not to bother
Most of the time, do nothing. Memoisation is not free, it costs memory and a comparison on every render, and wrapping cheap components in it can make an app slower, not faster. The honest workflow is to write the simple version first, and only reach for these tools when you have measured a real problem with the profiler. Premature memoisation is a classic way to add complexity for no gain. This ties back to how React re-renders, which is worth understanding before you try to optimise it.
Wrapping up
The three memoisation tools have clear jobs: useMemo caches an expensive value, useCallback keeps a function's identity stable, and React.memo lets a component skip a re-render when its props are unchanged. They are most useful together, because React.memo only helps when the props feeding it are stable, which is what the other two provide. Above all, do not reach for them by reflex. Measure first, optimise the real hotspots, and leave the cheap renders alone.
All comments ()
No comments yet
Be the first to leave a comment on this post.