React.memo โ Preventing Unnecessary Re-renders
React.memo is a Higher-Order Component (HOC) that memoises a component. When a parent re-renders, a memoised child only re-renders if its props actually changed (shallow comparison).
Without memo โ The Problem
function Parent() {
const [count, setCount] = useState(0);
return (
<>
<button onClick={() => setCount(c => c + 1)}>Count: {count}</button>
{/* ExpensiveChart re-renders on EVERY parent render, even though its props never change */}
<ExpensiveChart data={staticData} />
</>
);
}
With memo โ The Fix
// Wrap the component in React.memo
const ExpensiveChart = React.memo(function ExpensiveChart({ data, title }) {
console.log("Chart rendered");
return <canvas>{/* heavy rendering */}</canvas>;
});
// Now only re-renders if data or title props change
Custom Comparison Function
const UserRow = React.memo(
function UserRow({ user, onSelect }) {
return <tr onClick={() => onSelect(user.id)}><td>{user.name}</td></tr>;
},
// Return true to SKIP re-render (same as "are props equal?")
(prevProps, nextProps) =>
prevProps.user.id === nextProps.user.id &&
prevProps.user.updatedAt === nextProps.user.updatedAt
);
memo + useCallback
React.memo uses shallow comparison. If you pass a function as a prop, it will be a new reference on every render (breaking memo). Fix it with useCallback.
function Parent() {
const [count, setCount] = useState(0);
// Without useCallback: new function reference on every render โ memo is broken
// With useCallback: same reference โ memo works correctly
const handleSelect = useCallback((id) => {
console.log("Selected:", id);
}, []); // [] = stable forever
return (
<>
<button onClick={() => setCount(c => c + 1)}>{count}</button>
<UserRow user={user} onSelect={handleSelect} />
</>
);
}
TipDon’t wrap every component in
React.memo. Profile your app first with React DevTools Profiler, then optimise the components that actually render too often.