React Performance Optimization: Advanced Techniques for 2024
Master advanced React performance optimization techniques including memoization, code splitting, virtual scrolling, and server-side rendering strategies.
React Performance Optimization: Advanced Techniques for 2024
Performance is crucial for user experience, and React applications can benefit significantly from proper optimization techniques. This comprehensive guide covers advanced strategies to make your React apps lightning-fast.
Understanding React Performance
React's performance is primarily affected by:
- Re-renders: Unnecessary component updates
- Bundle size: Large JavaScript bundles slow initial load
- Network requests: Inefficient data fetching
- Memory usage: Leaks and excessive object creation
Memoization Strategies
React.memo for Component Memoization
// ✅ Optimized component with React.memo
const ExpensiveComponent = React.memo(({ data, onUpdate }) => {
console.log('ExpensiveComponent rendered');
return (
<div>
{data.map(item => (
<ComplexItem key={item.id} item={item} onUpdate={onUpdate} />
))}
</div>
);
});
// ✅ Custom comparison function
const OptimizedComponent = React.memo(({ user, settings }) => {
return <UserProfile user={user} settings={settings} />;
}, (prevProps, nextProps) => {
// Only re-render if user ID changes
return prevProps.user.id === nextProps.user.id;
});
useMemo for Expensive Calculations
function DataVisualization({ rawData, filters }) {
// ✅ Memoize expensive calculations
const processedData = useMemo(() => {
console.log('Processing data...');
return rawData
.filter(item => filters.includes(item.category))
.map(item => ({
...item,
processedValue: expensiveCalculation(item.value)
}))
.sort((a, b) => b.processedValue - a.processedValue);
}, [rawData, filters]);
const chartConfig = useMemo(() => ({
type: 'line',
data: processedData,
options: {
responsive: true,
plugins: {
legend: { position: 'top' }
}
}
}), [processedData]);
return <Chart {...chartConfig} />;
}
useCallback for Function References
function ParentComponent({ items }) {
const [filter, setFilter] = useState('');
const [sortOrder, setSortOrder] = useState('asc');
// ✅ Memoize callback functions
const handleItemClick = useCallback((itemId) => {
console.log('Item clicked:', itemId);
// Handle item click logic
}, []);
const handleSort = useCallback((newSortOrder) => {
setSortOrder(newSortOrder);
}, []);
const filteredItems = useMemo(() =>
items.filter(item =>
item.name.toLowerCase().includes(filter.toLowerCase())
), [items, filter]
);
return (
<div>
<SearchInput value={filter} onChange={setFilter} />
<SortControls onSort={handleSort} />
<ItemList
items={filteredItems}
onItemClick={handleItemClick}
/>
</div>
);
}
Code Splitting and Lazy Loading
Route-Based Code Splitting
import { lazy, Suspense } from 'react';
import { Routes, Route } from 'react-router-dom';
// ✅ Lazy load route components
const Dashboard = lazy(() => import('./pages/Dashboard'));
const Profile = lazy(() => import('./pages/Profile'));
const Analytics = lazy(() => import('./pages/Analytics'));
function App() {
return (
<Routes>
<Route path="/" element={
<Suspense fallback={<PageSkeleton />}>
<Dashboard />
</Suspense>
} />
<Route path="/profile" element={
<Suspense fallback={<PageSkeleton />}>
<Profile />
</Suspense>
} />
<Route path="/analytics" element={
<Suspense fallback={<AnalyticsSkeleton />}>
<Analytics />
</Suspense>
} />
</Routes>
);
}
Component-Based Code Splitting
// ✅ Split heavy components
const HeavyChart = lazy(() =>
import('./components/HeavyChart').then(module => ({
default: module.HeavyChart
}))
);
const HeavyTable = lazy(() => import('./components/HeavyTable'));
function Dashboard({ showChart, showTable }) {
return (
<div>
<h1>Dashboard</h1>
{showChart && (
<Suspense fallback={<ChartSkeleton />}>
<HeavyChart />
</Suspense>
)}
{showTable && (
<Suspense fallback={<TableSkeleton />}>
<HeavyTable />
</Suspense>
)}
</div>
);
}
Virtual Scrolling for Large Lists
import { FixedSizeList as List } from 'react-window';
function VirtualizedList({ items }) {
const Row = ({ index, style }) => (
<div style={style}>
<ListItem item={items[index]} />
</div>
);
return (
<List
height={600}
itemCount={items.length}
itemSize={80}
width="100%"
>
{Row}
</List>
);
}
// ✅ For dynamic heights
import { VariableSizeList as List } from 'react-window';
function DynamicVirtualizedList({ items }) {
const getItemSize = (index) => {
// Calculate height based on content
return items[index].type === 'image' ? 200 : 80;
};
return (
<List
height={600}
itemCount={items.length}
itemSize={getItemSize}
width="100%"
>
{Row}
</List>
);
}
Image Optimization
// ✅ Optimized image component
function OptimizedImage({ src, alt, ...props }) {
const [isLoaded, setIsLoaded] = useState(false);
const [isInView, setIsInView] = useState(false);
const imgRef = useRef();
useEffect(() => {
const observer = new IntersectionObserver(
([entry]) => {
if (entry.isIntersecting) {
setIsInView(true);
observer.disconnect();
}
},
{ threshold: 0.1 }
);
if (imgRef.current) {
observer.observe(imgRef.current);
}
return () => observer.disconnect();
}, []);
return (
<div ref={imgRef} className="relative">
{!isLoaded && <ImageSkeleton />}
{isInView && (
<img
src={src}
alt={alt}
onLoad={() => setIsLoaded(true)}
className={`transition-opacity ${isLoaded ? 'opacity-100' : 'opacity-0'}`}
{...props}
/>
)}
</div>
);
}
State Management Optimization
Optimizing Context Usage
// ✅ Split contexts by concern
const UserContext = createContext();
const ThemeContext = createContext();
const CartContext = createContext();
// ✅ Memoize context values
function UserProvider({ children }) {
const [user, setUser] = useState(null);
const value = useMemo(() => ({
user,
updateUser: setUser,
logout: () => setUser(null)
}), [user]);
return (
<UserContext.Provider value={value}>
{children}
</UserContext.Provider>
);
}
State Normalization
// ✅ Normalized state structure
const initialState = {
posts: {
byId: {},
allIds: []
},
comments: {
byId: {},
allIds: []
},
users: {
byId: {},
allIds: []
}
};
// ✅ Selectors for denormalized data
const selectPostWithComments = (state, postId) => {
const post = state.posts.byId[postId];
const comments = post.commentIds.map(id => state.comments.byId[id]);
return { ...post, comments };
};
Network Optimization
Request Batching
// ✅ Batch multiple requests
class RequestBatcher {
constructor(batchSize = 10, delay = 50) {
this.batchSize = batchSize;
this.delay = delay;
this.queue = [];
this.timeoutId = null;
}
add(request) {
return new Promise((resolve, reject) => {
this.queue.push({ request, resolve, reject });
if (this.queue.length >= this.batchSize) {
this.flush();
} else if (!this.timeoutId) {
this.timeoutId = setTimeout(() => this.flush(), this.delay);
}
});
}
async flush() {
if (this.queue.length === 0) return;
const batch = this.queue.splice(0, this.batchSize);
this.timeoutId = null;
try {
const requests = batch.map(({ request }) => request);
const responses = await Promise.all(requests);
batch.forEach(({ resolve }, index) => {
resolve(responses[index]);
});
} catch (error) {
batch.forEach(({ reject }) => reject(error));
}
}
}
Caching Strategies
// ✅ Simple cache implementation
class ApiCache {
constructor(ttl = 300000) { // 5 minutes
this.cache = new Map();
this.ttl = ttl;
}
set(key, data) {
this.cache.set(key, {
data,
timestamp: Date.now()
});
}
get(key) {
const item = this.cache.get(key);
if (!item) return null;
if (Date.now() - item.timestamp > this.ttl) {
this.cache.delete(key);
return null;
}
return item.data;
}
}
// ✅ Custom hook with caching
function useApiData(url, options = {}) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
const cachedData = apiCache.get(url);
if (cachedData) {
setData(cachedData);
setLoading(false);
return;
}
const fetchData = async () => {
try {
const response = await fetch(url, options);
const result = await response.json();
apiCache.set(url, result);
setData(result);
} catch (err) {
setError(err);
} finally {
setLoading(false);
}
};
fetchData();
}, [url, options]);
return { data, loading, error };
}
Performance Monitoring
Core Web Vitals Tracking
// ✅ Performance monitoring
function usePerformanceMonitoring() {
useEffect(() => {
// Largest Contentful Paint
new PerformanceObserver((entryList) => {
const entries = entryList.getEntries();
const lastEntry = entries[entries.length - 1];
console.log('LCP:', lastEntry.startTime);
}).observe({ entryTypes: ['largest-contentful-paint'] });
// First Input Delay
new PerformanceObserver((entryList) => {
const entries = entryList.getEntries();
entries.forEach((entry) => {
console.log('FID:', entry.processingStart - entry.startTime);
});
}).observe({ entryTypes: ['first-input'] });
// Cumulative Layout Shift
new PerformanceObserver((entryList) => {
let clsValue = 0;
entryList.getEntries().forEach((entry) => {
if (!entry.hadRecentInput) {
clsValue += entry.value;
}
});
console.log('CLS:', clsValue);
}).observe({ entryTypes: ['layout-shift'] });
}, []);
}
Best Practices Checklist
- ✅ Use React.memo for expensive components
- ✅ Implement useMemo for expensive calculations
- ✅ Use useCallback for event handlers
- ✅ Split code at route and component levels
- ✅ Implement virtual scrolling for large lists
- ✅ Optimize images with lazy loading
- ✅ Normalize state structure
- ✅ Batch API requests when possible
- ✅ Implement caching strategies
- ✅ Monitor Core Web Vitals
Conclusion
React performance optimization is an ongoing process that requires understanding your application's specific needs. Start with profiling to identify bottlenecks, then apply these techniques systematically.
Remember: measure first, optimize second. Use React DevTools Profiler to identify actual performance issues before applying optimizations.
Want to dive deeper into React performance? Check out my post on Advanced React Patterns for Large Applications for architectural strategies.