React performance optimization techniques
Performance Optimization in React
React is fast by default, but large apps can suffer from unnecessary re-renders, heavy computations, and large bundle sizes. Here are the key techniques to optimize React performance.
1. React.memo โ Prevent Unnecessary Re-renders
tsx
// Re-renders ONLY when props change (shallow comparison)
const ExpensiveList = React.memo(function ExpensiveList({ items }: { items: Item[] }) {
return (
<ul>
{items.map(item => <li key={item.id}>{item.name}</li>)}
</ul>
);
});2. useMemo โ Cache Expensive Computations
tsx
function Dashboard({ transactions }: { transactions: Transaction[] }) {
// Only recalculated when transactions change
const stats = useMemo(() => {
return {
total: transactions.reduce((sum, t) => sum + t.amount, 0),
average: transactions.reduce((sum, t) => sum + t.amount, 0) / transactions.length,
max: Math.max(...transactions.map(t => t.amount)),
};
}, [transactions]);
return <StatsCard stats={stats} />;
}3. useCallback โ Stable Function References
tsx
function Parent() {
const [count, setCount] = useState(0);
// Without useCallback: new function every render โ Child re-renders
// With useCallback: same function reference โ Child skips re-render
const handleClick = useCallback(() => {
console.log("clicked");
}, []); // Stable reference
return (
<div>
<span>{count}</span>
<button onClick={() => setCount(c => c + 1)}>+</button>
<MemoizedChild onClick={handleClick} />
</div>
);
}
const MemoizedChild = React.memo(function Child({ onClick }: { onClick: () => void }) {
console.log("Child rendered");
return <button onClick={onClick}>Click me</button>;
});4. Code Splitting with React.lazy
tsx
import { lazy, Suspense } from "react";
// Component is loaded only when needed
const HeavyChart = lazy(() => import("./HeavyChart"));
const AdminPanel = lazy(() => import("./AdminPanel"));
function App() {
return (
<Suspense fallback={<Loading />}>
<Routes>
<Route path="/dashboard" element={<HeavyChart />} />
<Route path="/admin" element={<AdminPanel />} />
</Routes>
</Suspense>
);
}5. Virtualization for Large Lists
tsx
import { useVirtualizer } from "@tanstack/react-virtual";
function VirtualList({ items }: { items: Item[] }) {
const parentRef = useRef<HTMLDivElement>(null);
const virtualizer = useVirtualizer({
count: items.length, // 10,000+ items
getScrollElement: () => parentRef.current,
estimateSize: () => 50, // Row height
});
return (
<div ref={parentRef} style={{ height: "400px", overflow: "auto" }}>
<div style={{ height: virtualizer.getTotalSize() }}>
{virtualizer.getVirtualItems().map(virtualRow => (
<div
key={virtualRow.key}
style={{
position: "absolute",
top: virtualRow.start,
height: virtualRow.size,
}}
>
{items[virtualRow.index].name}
</div>
))}
</div>
</div>
);
}6. Avoid Inline Objects and Functions
tsx
// โ New object every render โ child re-renders
<Child style={{ color: "red" }} />
<Child onClick={() => console.log("click")} />
// โ
Stable references
const style = useMemo(() => ({ color: "red" }), []);
const handleClick = useCallback(() => console.log("click"), []);
<Child style={style} />
<Child onClick={handleClick} />7. State Colocation
Keep state as close as possible to where it's used:
tsx
// โ State too high โ entire app re-renders on hover
function App() {
const [hoveredId, setHoveredId] = useState<string | null>(null);
return <List items={items} hoveredId={hoveredId} onHover={setHoveredId} />;
}
// โ
State colocated โ only ListItem re-renders
function ListItem({ item }: { item: Item }) {
const [isHovered, setIsHovered] = useState(false);
return (
<div
onMouseEnter={() => setIsHovered(true)}
onMouseLeave={() => setIsHovered(false)}
className={isHovered ? "highlighted" : ""}
>
{item.name}
</div>
);
}Quick Reference
| Problem | Solution |
|---|---|
| Child re-renders when parent re-renders | React.memo |
| Expensive calculation runs every render | useMemo |
| Callback prop causes re-render | useCallback + React.memo |
| Large initial bundle | Code splitting (React.lazy) |
| Rendering 10,000+ items | Virtualization |
| State changes cause widespread re-renders | State colocation |
| Heavy non-urgent updates | useTransition / useDeferredValue |
Important:
Don't optimize prematurely โ React is fast by default. Profile first with React DevTools Profiler, identify actual bottlenecks, then apply targeted optimizations. The biggest wins usually come from state colocation, code splitting, and virtualization rather than memo/useMemo everywhere.
Short Answer
Interview readyPremium
A concise answer to help you respond confidently on this topic during an interview.