When I Use React.memo
Tsx
// Parent re-renders often; this list is heavy and props are stable
function ExpensiveComponent({ data, onUpdate }) {
return (
<div>
{data.map(item => (
<div key={item.id}>{item.name}</div>
))}
</div>
);
}
const MemoizedExpensiveComponent = React.memo(ExpensiveComponent);
useMemo for Derived Data
Tsx
function ProductList({ products, filter, sortBy }) {
const filteredAndSortedProducts = useMemo(() => {
return products
.filter(product =>
product.name.toLowerCase().includes(filter.toLowerCase())
)
.sort((a, b) => {
if (sortBy === 'name') return a.name.localeCompare(b.name);
if (sortBy === 'price') return a.price - b.price;
return 0;
});
}, [products, filter, sortBy]);
return (
<div>
{filteredAndSortedProducts.map((product) => (
<ProductCard key={product.id} product={product} />
))}
</div>
);
}
useCallback for Stable Handlers
Tsx
function ParentComponent() {
const [count, setCount] = useState(0);
const [items, setItems] = useState([]);
const memoizedHandleAddItem = useCallback((item) => {
setItems(prev => [...prev, item]);
}, []);
return (
<div>
<Counter count={count} onIncrement={() => setCount((c) => c + 1)} />
<ItemList items={items} onAddItem={memoizedHandleAddItem} />
</div>
);
}
Code Splitting and Lazy Loading
Tsx
import { Suspense, lazy } from 'react';
const HeavyChart = lazy(() => import('./HeavyChart'));
const DataTable = lazy(() => import('./DataTable'));
function Dashboard() {
return (
<div>
<h1>Dashboard</h1>
<Suspense fallback={<div>Loading chart...</div>}>
<HeavyChart />
</Suspense>
<Suspense fallback={<div>Loading table...</div>}>
<DataTable />
</Suspense>
</div>
);
}
Virtual Scrolling for Long Lists
Tsx
function VirtualList({ items, itemHeight, containerHeight, renderItem }) {
const [scrollTop, setScrollTop] = useState(0);
const visibleCount = Math.ceil(containerHeight / itemHeight);
const startIndex = Math.floor(scrollTop / itemHeight);
const endIndex = Math.min(startIndex + visibleCount + 1, items.length);
const visibleItems = items.slice(startIndex, endIndex);
const offsetY = startIndex * itemHeight;
return (
<div
style={{ height: containerHeight, overflow: 'auto' }}
onScroll={(e) => setScrollTop(e.currentTarget.scrollTop)}
>
<div style={{ height: items.length * itemHeight, position: 'relative' }}>
<div style={{ transform: `translateY(${offsetY}px)` }}>
{visibleItems.map((item, i) => (
<div key={startIndex + i} style={{ height: itemHeight }}>
{renderItem(item)}
</div>
))}
</div>
</div>
</div>
);
}
Profiling Before Optimizing
Tsx
import { Profiler } from 'react';
function onRenderCallback(id, phase, actualDuration, baseDuration, startTime, commitTime) {
console.log('Component:', id);
console.log('Phase:', phase);
console.log('Actual duration:', actualDuration);
console.log('Base duration:', baseDuration);
}
function App() {
return (
<Profiler id='App' onRender={onRenderCallback}>
<YourComponent />
</Profiler>
);
}