Understanding React's Rendering
Memoization Techniques
React.memo for Component Memoization
Component Memoization
// Without memoization - re-renders on every parent update
function ExpensiveComponent({ data, onUpdate }) {
return (
<div>
{data.map(item => (
<div key={item.id}>{item.name}</div>
))}
</div>
);
}
// With memoization - only re-renders when props change
const MemoizedExpensiveComponent = React.memo(ExpensiveComponent);useMemo for Expensive Calculations
useMemo Example
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 Function Memoization
useCallback Example
function ParentComponent() {
const [count, setCount] = useState(0);
const [items, setItems] = useState([]);
// With useCallback - function is memoized
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
Dynamic Imports with React.lazy
Lazy Loading
import { Suspense, lazy } from 'react';
// Lazy load heavy components
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 Large Lists
Virtual Scrolling
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);
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(${startIndex * itemHeight}px)` }}>
{visibleItems.map((item, index) => (
<div key={startIndex + index} style={{ height: itemHeight }}>
{renderItem(item, startIndex + index)}
</div>
))}
</div>
</div>
</div>
);
}Performance Monitoring
React DevTools Profiler
Performance Monitoring
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>
); }Best Practices
- Measure First - Always profile before optimizing
- Memoize Wisely - Don't overuse memoization
- Split Code - Use code splitting for better initial load times
- Optimize Images - Use Next.js Image component
- Minimize Re-renders - Keep state close to where it's used
- Use Keys Properly - Stable keys for list items
- Avoid Inline Objects - Don't create objects in render methods
- Lazy Load - Load components and routes on demand