Learn when React.memo, useMemo, and useCallback genuinely speed up your app, and when they complicate it for nothing.
Open this lesson in KodokonWhen a component re-renders, all of its children re-render by default, whether their props changed or not. That is not a tragedy: a render only touches the DOM if the output changes. Memoization becomes relevant when frequent renders meet expensive subtrees. Three tools, three targets: React.memo skips a component's render if its props are shallowly equal, useMemo caches a computed value, useCallback caches a function. All three rely on reference equality.
import { memo } from "react";
const Row = memo(function Row({ item, onSelect }) {
return (
<li onClick={() => onSelect(item.id)}>{item.label}</li>
);
});The chain is only as strong as its weakest link. If a single prop receives a fresh reference on every render - an inline arrow function, an object or array literal - memo's comparison fails and the component re-renders anyway. useCallback stabilizes the function's reference so the comparison succeeds.
import { useCallback } from "react";
function List({ items }) {
const handleSelect = useCallback((id) => {
console.log("selected:", id);
}, []);
return (
<ul>
{items.map((item) => (
<Row
key={item.id}
item={item}
onSelect={handleSelect}
/>
))}
</ul>
);
}useMemo has two legitimate uses: avoiding a genuinely expensive computation (sorting or aggregating thousands of items), and stabilizing a reference consumed by a memoized child or listed in an effect's dependencies. For a trivial computation, the cache costs more than it saves: dependency comparison, retained memory, heavier reading.
import { useMemo } from "react";
function Stats({ orders }) {
const total = useMemo(() => {
return orders.reduce(
(sum, order) => sum + order.amount,
0
);
}, [orders]);
return <p>Total: {total} €</p>;
}