Why? Avoids stale closures and premature optimisation overhead.
Premature optimization is a classic rabbit hole. It’s tempting to sprinkle useMemo
and wrap every component in React.memo
“just in case.” The cost? Extra code to read, risk of stale closures, and more memory usage—sometimes even slower renders when dependencies change. Memoization shines when a calculation is expensive or a prop‑drilling chain triggers costly re‑renders and you can prove it in React DevTools. Otherwise, the safest—and fastest—code is the simplest one.
import { useMemo } from "react";
type Order = { id: number; total: number; date: string };
function useOrderStats(orders: Order[]) {
// Heavy calculation (e.g., thousands of orders) — legit candidate for useMemo
return useMemo(() => {
const totals = orders.map((o) => o.total);
const sum = totals.reduce((s, t) => s + t, 0);
return {
count: orders.length,
average: sum / orders.length || 0,
max: Math.max(...totals),
};
}, [orders]);
}
export default function Dashboard({ orders }: { orders: Order[] }) {
const stats = useOrderStats(orders);
return (
<section className="rounded-xl border p-6">
<h2 className="text-lg font-semibold">Order Stats</h2>
<p>Total orders: {stats.count}</p>
<p>Average value: ${stats.average.toFixed(2)}</p>
<p>Largest order: ${stats.max.toFixed(2)}</p>
</section>
);
}
Why this is good
useMemo
.orders
matter; memo stays valid until the list truly changes.// Over‑memoization everywhere
const FancyButton = React.memo(function FancyButton(props: { label: string }) {
console.log("FancyButton render"); // still logs on every parent re-render
return (
<button className="rounded bg-blue-600 px-4 py-2 text-white">
{props.label}
</button>
);
});
export default function Form() {
const [name, setName] = useState("");
// ❌ unnecessary useCallback: setName already stable from React
const handleChange = useCallback(
(e: React.ChangeEvent<HTMLInputElement>) => setName(e.target.value),
[]
);
// ❌ useMemo wraps a trivial string concat
const greeting = useMemo(() => `Hello, ${name}!`, [name]);
return (
<>
<input value={name} onChange={handleChange} className="border p-2" />
<p>{greeting}</p> {/* useless memoization */}
<FancyButton label="Submit" />
{/* memoized component still re-renders because props change? none! */}
</>
);
}
// Outcome: more code, zero performance gain, and potential stale-bug
// risks if dependencies are forgotten later.
React.memo
≠ free pass: Components inside still re‑render if shallow‑compared props change or context updates.