React Performance: Avoiding Unnecessary Re-Renders With memo and useMemo
Re-renders aren't inherently bad — React is fast. But unnecessary re-renders of expensive components cause jank. Here's how to fix them.
When to Optimize
Profile first. Use React DevTools Profiler to identify components that:
- Render frequently (dozens of times per second)
- Are expensive to render (large lists, complex calculations)
- Re-render when their props haven't changed
Don't add memo everywhere — it adds overhead and rarely helps for cheap components.
React.memo: Skip Re-Renders When Props Unchanged
// Without memo: re-renders every time parent renders
function ExpensiveChart({ data }: { data: number[] }) {
return <svg>{/* complex rendering */}</svg>;
}
// With memo: only re-renders when data changes
const ExpensiveChart = React.memo(function ExpensiveChart({ data }: { data: number[] }) {
return <svg>{/* complex rendering */}</svg>;
});
// Custom comparison (when shallow equality isn't enough)
const ExpensiveChart = React.memo(
function ExpensiveChart({ data }: { data: number[] }) {
return <svg>{/* */}</svg>;
},
(prevProps, nextProps) => {
return prevProps.data.length === nextProps.data.length &&
prevProps.data.every((v, i) => v === nextProps.data[i]);
}
);
useMemo: Cache Expensive Calculations
function Dashboard({ transactions }: { transactions: Transaction[] }) {
// Recalculates on EVERY render without useMemo
const stats = transactions.reduce((acc, t) => {
// expensive aggregation...
}, initialStats);
// With useMemo: only recalculates when transactions changes
const stats = useMemo(
() => transactions.reduce((acc, t) => { /* expensive */ }, initialStats),
[transactions]
);
return <StatsDisplay stats={stats} />;
}
useCallback: Stable Function References
function Parent() {
const [count, setCount] = useState(0);
// Bad: new function reference on every render
// breaks React.memo on Child
const handleClick = () => setCount(c => c + 1);
// Good: stable reference
const handleClick = useCallback(() => setCount(c => c + 1), []);
return <MemoizedChild onClick={handleClick} />;
}
The Virtualization Alternative
For long lists (500+ items), virtualization beats memo:
import { FixedSizeList } from 'react-window';
function VirtualizedList({ items }: { items: Item[] }) {
return (
<FixedSizeList height={600} itemCount={items.length} itemSize={50} width='100%'>
{({ index, style }) => (
<div style={style}>{items[index].name}</div>
)}
</FixedSizeList>
);
// Only renders ~12 visible items, regardless of list size
}
Performance optimization patterns — memo, virtualization, code splitting — are part of the production React setup in the AI SaaS Starter Kit.
Top comments (0)