React Performance Optimization: Advanced Techniques
Deep dive into advanced React performance optimization techniques including memoization, code splitting, and virtual scrolling.
React Performance Optimization: Advanced Techniques
Performance optimization in React applications is crucial for delivering excellent user experiences. Let's explore advanced techniques that can dramatically improve your app's performance.
Understanding React's Rendering Process
Before optimizing, it's important to understand when and why React re-renders components:
// This component re-renders whenever the parent re-renders
function ExpensiveComponent({ data }) {
const expensiveValue = computeExpensiveValue(data);
return <div>{expensiveValue}</div>;
}
Memoization Techniques
React.memo for Component Memoization
import React, { memo } from 'react';
const ExpensiveComponent = memo(({ data, onUpdate }) => {
const expensiveValue = computeExpensiveValue(data);
return (
<div onClick={() => onUpdate(data.id)}>
{expensiveValue}
</div>
);
}, (prevProps, nextProps) => {
// Custom comparison function
return prevProps.data.id === nextProps.data.id;
});
useMemo for Expensive Calculations
import { useMemo } from 'react';
function DataVisualization({ rawData, filters }) {
const processedData = useMemo(() => {
return rawData
.filter(item => filters.includes(item.category))
.map(item => transformData(item))
.sort((a, b) => b.value - a.value);
}, [rawData, filters]);
return <Chart data={processedData} />;
}
useCallback for Function Memoization
import { useCallback, useState } from 'react';
function TodoList({ todos }) {
const [filter, setFilter] = useState('all');
const handleToggle = useCallback((id) => {
setTodos(prev => prev.map(todo =>
todo.id === id ? { ...todo, completed: !todo.completed } : todo
));
}, []);
const filteredTodos = useMemo(() => {
return todos.filter(todo => {
if (filter === 'completed') return todo.completed;
if (filter === 'active') return !todo.completed;
return true;
});
}, [todos, filter]);
return (
<div>
{filteredTodos.map(todo => (
<TodoItem
key={todo.id}
todo={todo}
onToggle={handleToggle}
/>
))}
</div>
);
}
Code Splitting and Lazy Loading
Component-Level Code Splitting
import { lazy, Suspense } from 'react';
const HeavyComponent = lazy(() => import('./HeavyComponent'));
const Dashboard = lazy(() => import('./Dashboard'));
function App() {
return (
<Router>
<Routes>
<Route
path="/dashboard"
element={
<Suspense fallback={<div>Loading dashboard...</div>}>
<Dashboard />
</Suspense>
}
/>
<Route
path="/heavy"
element={
<Suspense fallback={<div>Loading...</div>}>
<HeavyComponent />
</Suspense>
}
/>
</Routes>
</Router>
);
}
Dynamic Imports with Conditions
import { useState } from 'react';
function AdvancedEditor() {
const [showAdvanced, setShowAdvanced] = useState(false);
const [AdvancedEditor, setAdvancedEditor] = useState(null);
const loadAdvancedEditor = async () => {
if (!AdvancedEditor) {
const module = await import('./AdvancedEditor');
setAdvancedEditor(() => module.default);
}
setShowAdvanced(true);
};
return (
<div>
<BasicEditor />
{!showAdvanced && (
<button onClick={loadAdvancedEditor}>
Load Advanced Editor
</button>
)}
{showAdvanced && AdvancedEditor && <AdvancedEditor />}
</div>
);
}
Virtual Scrolling for Large Lists
import { FixedSizeList as List } from 'react-window';
function VirtualizedList({ items }) {
const Row = ({ index, style }) => (
<div style={style}>
<ItemComponent item={items[index]} />
</div>
);
return (
<List
height={600}
itemCount={items.length}
itemSize={50}
width="100%"
>
{Row}
</List>
);
}
Image Optimization
Next.js Image Component
import Image from 'next/image';
function OptimizedGallery({ photos }) {
return (
<div className="grid grid-cols-3 gap-4">
{photos.map((photo) => (
<Image
key={photo.id}
src={photo.src}
alt={photo.alt}
width={400}
height={300}
loading="lazy"
placeholder="blur"
blurDataURL="data:image/jpeg;base64,..."
/>
))}
</div>
);
}
Progressive Image Loading
import { useState } from 'react';
function ProgressiveImage({ src, placeholder, alt }) {
const [loaded, setLoaded] = useState(false);
return (
<div className="relative">
<img
src={placeholder}
alt={alt}
className={`transition-opacity ${loaded ? 'opacity-0' : 'opacity-100'}`}
/>
<img
src={src}
alt={alt}
className={`absolute inset-0 transition-opacity ${loaded ? 'opacity-100' : 'opacity-0'}`}
onLoad={() => setLoaded(true)}
/>
</div>
);
}
State Management Optimization
Context Splitting
// Split contexts to prevent unnecessary re-renders
const UserContext = createContext();
const ThemeContext = createContext();
const NotificationContext = createContext();
function AppProviders({ children }) {
return (
<UserProvider>
<ThemeProvider>
<NotificationProvider>
{children}
</NotificationProvider>
</ThemeProvider>
</UserProvider>
);
}
Reducer with Immer
import { useReducer } from 'react';
import { produce } from 'immer';
const todoReducer = produce((draft, action) => {
switch (action.type) {
case 'ADD_TODO':
draft.push({
id: Date.now(),
text: action.text,
completed: false
});
break;
case 'TOGGLE_TODO':
const todo = draft.find(t => t.id === action.id);
if (todo) {
todo.completed = !todo.completed;
}
break;
}
});
Performance Monitoring
Custom Performance Hook
import { useEffect, useRef } from 'react';
function usePerformanceMonitor(name) {
const renderStart = useRef(performance.now());
useEffect(() => {
const renderTime = performance.now() - renderStart.current;
console.log(`${name} render time: ${renderTime.toFixed(2)}ms`);
});
useEffect(() => {
renderStart.current = performance.now();
});
}
function ExpensiveComponent() {
usePerformanceMonitor('ExpensiveComponent');
// Component logic here
return <div>Expensive Component</div>;
}
React DevTools Profiler
import { Profiler } from 'react';
function onRenderCallback(id, phase, actualDuration) {
console.log(`Component ${id} ${phase} phase took ${actualDuration}ms`);
}
function App() {
return (
<Profiler id="App" onRender={onRenderCallback}>
<MainContent />
</Profiler>
);
}
Best Practices Summary
- Measure First: Use React DevTools Profiler to identify bottlenecks
- Avoid Premature Optimization: Only optimize components that actually need it
- Use Memoization Wisely: Don't memoize everything; it has overhead
- Split Large Components: Keep components focused and small
- Optimize Bundle Size: Use code splitting and tree shaking
- Monitor Performance: Set up performance budgets and monitoring
Conclusion
React performance optimization is an ongoing process. Start with measuring your app's performance, identify bottlenecks, and apply these techniques strategically. Remember, the best optimization is often avoiding unnecessary work altogether.
By implementing these advanced techniques, you can build React applications that are not only feature-rich but also incredibly fast and responsive.