Performance
React performance optimization techniques, profiling, memoization, and best practices for building fast applications.
Performance Optimization Techniques
React.memo
import React from 'react';
// Basic memoization
const ExpensiveComponent = React.memo(function ExpensiveComponent({ data }) {
console.log('ExpensiveComponent rendered');
return (
<div>
{data.map(item => (
<div key={item.id}>{item.name}</div>
))}
</div>
);
});
// Custom comparison function
const OptimizedComponent = React.memo(
function OptimizedComponent({ user, settings }) {
return (
<div>
<h1>{user.name}</h1>
<p>Theme: {settings.theme}</p>
</div>
);
},
(prevProps, nextProps) => {
// Only re-render if user ID or theme changes
return (
prevProps.user.id === nextProps.user.id &&
prevProps.settings.theme === nextProps.settings.theme
);
}
);
useMemo Hook
import { useMemo, useState } from 'react';
function ExpensiveCalculation({ items, multiplier, filter }) {
const [sortOrder, setSortOrder] = useState('asc');
// Memoize expensive filtering and sorting
const processedItems = useMemo(() => {
console.log('Processing items...');
// Filter items
const filtered = items.filter(item =>
item.name.toLowerCase().includes(filter.toLowerCase())
);
// Sort items
const sorted = filtered.sort((a, b) => {
const comparison = a.name.localeCompare(b.name);
return sortOrder === 'asc' ? comparison : -comparison;
});
// Apply multiplier
return sorted.map(item => ({
...item,
value: item.value * multiplier,
}));
}, [items, multiplier, filter, sortOrder]);
return (
<div>
<select value={sortOrder} onChange={e => setSortOrder(e.target.value)}>
<option value="asc">Ascending</option>
<option value="desc">Descending</option>
</select>
<ul>
{processedItems.map(item => (
<li key={item.id}>
{item.name}: {item.value}
</li>
))}
</ul>
</div>
);
}
useCallback Hook
import { useCallback, useState, memo } from 'react';
// Child component that should not re-render unnecessarily
const ListItem = memo(function ListItem({ item, onEdit, onDelete }) {
console.log('ListItem rendered for:', item.name);
return (
<li>
<span>{item.name}</span>
<button onClick={() => onEdit(item.id)}>Edit</button>
<button onClick={() => onDelete(item.id)}>Delete</button>
</li>
);
});
function ItemList({ items }) {
const [editMode, setEditMode] = useState(null);
const [filter, setFilter] = useState('');
// Memoize callbacks to prevent child re-renders
const handleEdit = useCallback(id => {
setEditMode(id);
}, []);
const handleDelete = useCallback(id => {
// Delete logic here
console.log('Deleting item:', id);
}, []);
const filteredItems = items.filter(item =>
item.name.toLowerCase().includes(filter.toLowerCase())
);
return (
<div>
<input
value={filter}
onChange={e => setFilter(e.target.value)}
placeholder="Filter items..."
/>
<ul>
{filteredItems.map(item => (
<ListItem
key={item.id}
item={item}
onEdit={handleEdit}
onDelete={handleDelete}
/>
))}
</ul>
</div>
);
}
Code Splitting
Lazy Loading Components
import { lazy, Suspense } from 'react';
// Lazy load components
const Dashboard = lazy(() => import('./Dashboard'));
const Profile = lazy(() => import('./Profile'));
const Settings = lazy(() => import('./Settings'));
// Loading component
function LoadingSpinner() {
return (
<div className="loading">
<div className="spinner"></div>
<p>Loading...</p>
</div>
);
}
function App() {
return (
<div>
<header>
<nav>
<Link to="/dashboard">Dashboard</Link>
<Link to="/profile">Profile</Link>
<Link to="/settings">Settings</Link>
</nav>
</header>
<main>
<Suspense fallback={<LoadingSpinner />}>
<Routes>
<Route path="/dashboard" element={<Dashboard />} />
<Route path="/profile" element={<Profile />} />
<Route path="/settings" element={<Settings />} />
</Routes>
</Suspense>
</main>
</div>
);
}
Dynamic Imports
import { useState } from 'react';
function DynamicComponent() {
const [component, setComponent] = useState(null);
const [loading, setLoading] = useState(false);
const loadComponent = async componentName => {
setLoading(true);
try {
const module = await import(`./components/${componentName}`);
setComponent(() => module.default);
} catch (error) {
console.error('Failed to load component:', error);
} finally {
setLoading(false);
}
};
return (
<div>
<button onClick={() => loadComponent('Chart')}>Load Chart</button>
<button onClick={() => loadComponent('Table')}>Load Table</button>
{loading && <div>Loading component...</div>}
{component && <component />}
</div>
);
}
Bundle Optimization
Tree Shaking
// ❌ Bad: Imports entire library
import _ from 'lodash';
const result = _.debounce(func, 300);
// ✅ Good: Import only what you need
import debounce from 'lodash/debounce';
const result = debounce(func, 300);
// ✅ Good: Use ES6 imports for tree shaking
import { debounce } from 'lodash-es';
Webpack Bundle Analyzer
# Install bundle analyzer
npm install --save-dev webpack-bundle-analyzer
# Add to package.json scripts
"analyze": "npm run build && npx webpack-bundle-analyzer build/static/js/*.js"
# Run analysis
npm run analyze
Code Splitting Strategies
// Route-based splitting
const Home = lazy(() => import('./pages/Home'));
const About = lazy(() => import('./pages/About'));
// Feature-based splitting
const AdminPanel = lazy(() => import('./features/AdminPanel'));
const UserDashboard = lazy(() => import('./features/UserDashboard'));
// Component-based splitting
const HeavyChart = lazy(() => import('./components/HeavyChart'));
const DataTable = lazy(() => import('./components/DataTable'));
Profiling and Debugging
React DevTools Profiler
// Enable profiling in development
function App() {
return (
<React.Profiler id="App" onRender={onRenderCallback}>
<div className="App">
<Header />
<Main />
<Footer />
</div>
</React.Profiler>
);
}
function onRenderCallback(
id,
phase,
actualDuration,
baseDuration,
startTime,
commitTime,
interactions
) {
console.log('Profiler:', {
id,
phase,
actualDuration,
baseDuration,
startTime,
commitTime,
interactions,
});
}
Performance Monitoring
import { getCLS, getFID, getFCP, getLCP, getTTFB } from 'web-vitals';
function reportWebVitals(metric) {
console.log(metric);
// Send to analytics service
analytics.track('web-vital', metric);
}
// Measure Core Web Vitals
getCLS(reportWebVitals);
getFID(reportWebVitals);
getFCP(reportWebVitals);
getLCP(reportWebVitals);
getTTFB(reportWebVitals);
Custom Performance Hooks
import { useEffect, useRef } from 'react';
// Hook to measure render time
function useRenderTime(componentName) {
const renderStart = useRef(performance.now());
useEffect(() => {
const renderEnd = performance.now();
const renderTime = renderEnd - renderStart.current;
console.log(`${componentName} render time: ${renderTime.toFixed(2)}ms`);
// Reset for next render
renderStart.current = performance.now();
});
}
// Hook to detect slow renders
function useSlowRenderDetector(threshold = 16) {
const renderStart = useRef(performance.now());
useEffect(() => {
const renderEnd = performance.now();
const renderTime = renderEnd - renderStart.current;
if (renderTime > threshold) {
console.warn(`Slow render detected: ${renderTime.toFixed(2)}ms`);
}
renderStart.current = performance.now();
});
}
// Usage
function MyComponent() {
useRenderTime('MyComponent');
useSlowRenderDetector(20);
return <div>My Component</div>;
}
State Management Performance
Context Optimization
// ❌ Bad: Single large context
const AppContext = createContext();
function AppProvider({ children }) {
const [user, setUser] = useState(null);
const [theme, setTheme] = useState('light');
const [notifications, setNotifications] = useState([]);
// All components re-render when any value changes
return (
<AppContext.Provider
value={{
user,
setUser,
theme,
setTheme,
notifications,
setNotifications,
}}
>
{children}
</AppContext.Provider>
);
}
// ✅ Good: Split contexts by update frequency
const UserContext = createContext();
const ThemeContext = createContext();
const NotificationContext = createContext();
function UserProvider({ children }) {
const [user, setUser] = useState(null);
return (
<UserContext.Provider value={{ user, setUser }}>
{children}
</UserContext.Provider>
);
}
function ThemeProvider({ children }) {
const [theme, setTheme] = useState('light');
return (
<ThemeContext.Provider value={{ theme, setTheme }}>
{children}
</ThemeContext.Provider>
);
}
State Selector Optimization
// Using React-Redux with selectors
import { useSelector } from 'react-redux';
import { createSelector } from 'reselect';
// Memoized selector
const selectFilteredItems = createSelector(
state => state.items,
state => state.filter,
(items, filter) =>
items.filter(item => item.name.toLowerCase().includes(filter.toLowerCase()))
);
function ItemList() {
const filteredItems = useSelector(selectFilteredItems);
const filter = useSelector(state => state.filter);
return (
<div>
{filteredItems.map(item => (
<div key={item.id}>{item.name}</div>
))}
</div>
);
}
List Optimization
Virtualization
// Using react-window for large lists
import { FixedSizeList as List } from 'react-window';
function VirtualizedList({ items }) {
const Row = ({ index, style }) => (
<div style={style}>{items[index].name}</div>
);
return (
<List height={600} itemCount={items.length} itemSize={50} width="100%">
{Row}
</List>
);
}
Pagination
function PaginatedList({ items, pageSize = 10 }) {
const [currentPage, setCurrentPage] = useState(1);
const paginatedItems = useMemo(() => {
const startIndex = (currentPage - 1) * pageSize;
const endIndex = startIndex + pageSize;
return items.slice(startIndex, endIndex);
}, [items, currentPage, pageSize]);
const totalPages = Math.ceil(items.length / pageSize);
return (
<div>
<ul>
{paginatedItems.map(item => (
<li key={item.id}>{item.name}</li>
))}
</ul>
<div className="pagination">
<button
onClick={() => setCurrentPage(prev => Math.max(1, prev - 1))}
disabled={currentPage === 1}
>
Previous
</button>
<span>
Page {currentPage} of {totalPages}
</span>
<button
onClick={() => setCurrentPage(prev => Math.min(totalPages, prev + 1))}
disabled={currentPage === totalPages}
>
Next
</button>
</div>
</div>
);
}
Image and Asset Optimization
Lazy Loading Images
import { useState, useRef, useEffect } from 'react';
function LazyImage({ src, alt, placeholder, ...props }) {
const [imageSrc, setImageSrc] = useState(placeholder);
const [loading, setLoading] = useState(true);
const imageRef = useRef();
useEffect(() => {
const observer = new IntersectionObserver(
([entry]) => {
if (entry.isIntersecting) {
setImageSrc(src);
observer.disconnect();
}
},
{ threshold: 0.1 }
);
if (imageRef.current) {
observer.observe(imageRef.current);
}
return () => observer.disconnect();
}, [src]);
return (
<img
ref={imageRef}
src={imageSrc}
alt={alt}
loading="lazy"
onLoad={() => setLoading(false)}
style={{
filter: loading ? 'blur(5px)' : 'none',
transition: 'filter 0.3s',
}}
{...props}
/>
);
}
Image Optimization
// Using next/image for optimization
import Image from 'next/image';
function OptimizedImage({ src, alt, width, height }) {
return (
<Image
src={src}
alt={alt}
width={width}
height={height}
placeholder="blur"
blurDataURL="data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAYEBQYFBAYGBQYHBwYIChAKCgkJChQODwwQFxQYGBcUFhYaHSUfGhsjHBYWICwgIyYnKSopGR8tMC0oMCUoKSj/2wBDAQcHBwoIChMKChMoGhYaKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCj/wAARCAAIAAoDASIAAhEBAxEB/8QAFQABAQAAAAAAAAAAAAAAAAAAAAv/xAAhEAACAQMDBQAAAAAAAAAAAAABAgMABAUGIWEREiMxUf/EABUBAQEAAAAAAAAAAAAAAAAAAAMF/8QAGhEAAgIDAAAAAAAAAAAAAAAAAAECEgMRkf/aAAwDAQACEQMRAD8AltJagyeH0AthI5xdrLcNM91BF5pX2HaH9bcfaSXWGaRmknyJckliyjqTzSlT54b6bk+h0R+hBeCNvEMt7g=="
priority
/>
);
}
Common Performance Anti-patterns
Avoid These Patterns
// ❌ Bad: Inline object creation
function Component() {
return (
<div>
<ChildComponent style={{ color: 'red' }} />
<ChildComponent config={{ theme: 'dark' }} />
</div>
);
}
// ✅ Good: Extract objects
const redStyle = { color: 'red' };
const darkConfig = { theme: 'dark' };
function Component() {
return (
<div>
<ChildComponent style={redStyle} />
<ChildComponent config={darkConfig} />
</div>
);
}
// ❌ Bad: Inline function creation
function Component({ items }) {
return (
<div>
{items.map(item => (
<button key={item.id} onClick={() => handleClick(item.id)}>
{item.name}
</button>
))}
</div>
);
}
// ✅ Good: Use useCallback
function Component({ items }) {
const handleClick = useCallback(id => {
// Handle click
}, []);
return (
<div>
{items.map(item => (
<button key={item.id} onClick={() => handleClick(item.id)}>
{item.name}
</button>
))}
</div>
);
}
Unnecessary Re-renders
// ❌ Bad: Creating new objects in render
function Component({ user }) {
const userInfo = {
name: user.name,
email: user.email,
lastLogin: new Date(user.lastLogin).toLocaleDateString(),
};
return <UserCard user={userInfo} />;
}
// ✅ Good: Use useMemo
function Component({ user }) {
const userInfo = useMemo(
() => ({
name: user.name,
email: user.email,
lastLogin: new Date(user.lastLogin).toLocaleDateString(),
}),
[user.name, user.email, user.lastLogin]
);
return <UserCard user={userInfo} />;
}
Performance Monitoring
Real User Monitoring
// Monitor component performance
function usePerformanceMonitor(componentName) {
const startTime = useRef(performance.now());
useEffect(() => {
const endTime = performance.now();
const duration = endTime - startTime.current;
// Send to analytics
analytics.timing('component.render', duration, {
component: componentName,
});
startTime.current = performance.now();
});
}
// Track user interactions
function useInteractionTracking() {
useEffect(() => {
const handleClick = event => {
const start = performance.now();
// Measure time to next paint
requestAnimationFrame(() => {
const end = performance.now();
analytics.timing('interaction.response', end - start);
});
};
document.addEventListener('click', handleClick);
return () => document.removeEventListener('click', handleClick);
}, []);
}
Error Boundary Performance
class PerformanceErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error) {
return { hasError: true };
}
componentDidCatch(error, errorInfo) {
// Log performance-related errors
console.error('Performance Error:', error, errorInfo);
// Send to error tracking service
errorTracker.captureException(error, {
extra: errorInfo,
tags: { type: 'performance' },
});
}
render() {
if (this.state.hasError) {
return (
<div className="error-fallback">
<h2>Something went wrong</h2>
<p>The application encountered a performance issue.</p>
<button onClick={() => window.location.reload()}>Reload Page</button>
</div>
);
}
return this.props.children;
}
}
Quick Reference
Optimization Checklist
// ✅ Memoization
const MemoizedComponent = React.memo(MyComponent);
const memoizedValue = useMemo(() => expensiveCalculation(a, b), [a, b]);
const memoizedCallback = useCallback(() => doSomething(a, b), [a, b]);
// ✅ Code splitting
const LazyComponent = lazy(() => import('./LazyComponent'));
// ✅ Avoid inline objects/functions
const style = { color: 'red' };
const handleClick = useCallback(() => {}, []);
// ✅ Optimize lists
<VirtualizedList items={items} />
// ✅ Image optimization
<img loading="lazy" src={src} alt={alt} />
Performance Hooks
// Monitor render time
function useRenderTime(name) {
const start = useRef(performance.now());
useEffect(() => {
console.log(`${name}: ${performance.now() - start.current}ms`);
});
}
// Debounce updates
function useDebounce(value, delay) {
const [debouncedValue, setDebouncedValue] = useState(value);
useEffect(() => {
const handler = setTimeout(() => {
setDebouncedValue(value);
}, delay);
return () => clearTimeout(handler);
}, [value, delay]);
return debouncedValue;
}