React Performance Optimization
Comprehensive guide to optimizing React applications for better performance, user experience, and scalability:
React.memo() - Preventing Unnecessary Re-renders
React.memo() is a higher-order component that memoizes the result of a component. It only re-renders if its props have changed, helping to prevent unnecessary re-renders.
// Basic memo usage
const ExpensiveComponent = React.memo(({ data, onUpdate }) => {
console.log('Rendering expensive component');
return (
<div>
<h2>{data.title}</h2>
<p>{data.description}</p>
<button onClick={onUpdate}>Update</button>
</div>
);
});
// Custom comparison function
const CustomMemoComponent = React.memo(
({ user, settings }) => {
return <UserProfile user={user} settings={settings} />;
},
(prevProps, nextProps) => {
// Custom comparison logic
return prevProps.user.id === nextProps.user.id &&
prevProps.settings.theme === nextProps.settings.theme;
}
);useMemo() - Memoizing Expensive Calculations
useMemo memoizes the result of a computation and only recalculates when dependencies change. It helps optimize expensive calculations and object creation.
function ProductList({ products, filters, sortBy }) {
// Expensive calculation - only runs when dependencies change
const filteredAndSortedProducts = useMemo(() => {
console.log('Filtering and sorting products');
return products
.filter(product => {
return filters.category === 'all' || product.category === filters.category;
})
.filter(product => product.price >= filters.minPrice)
.sort((a, b) => {
if (sortBy === 'price') return a.price - b.price;
if (sortBy === 'name') return a.name.localeCompare(b.name);
return 0;
});
}, [products, filters, sortBy]);
return (
<div>
{filteredAndSortedProducts.map(product => (
<ProductCard key={product.id} product={product} />
))}
</div>
);
}useCallback() - Memoizing Functions
useCallback memoizes a function and only creates a new function when dependencies change. It helps prevent unnecessary re-renders of child components that depend on the function.
function TodoApp({ todos }) {
const [filter, setFilter] = useState('all');
// Memoized callback - only changes when todos change
const handleToggleTodo = useCallback((id) => {
setTodos(prevTodos =>
prevTodos.map(todo =>
todo.id === id ? { ...todo, completed: !todo.completed } : todo
)
);
}, []);
// Memoized callback with dependencies
const handleAddTodo = useCallback((text) => {
const newTodo = {
id: Date.now(),
text,
completed: false,
createdAt: new Date()
};
setTodos(prevTodos => [...prevTodos, newTodo]);
}, []);
return (
<div>
<TodoList
todos={todos}
onToggle={handleToggleTodo}
onAdd={handleAddTodo}
/>
</div>
);
}Code Splitting with React.lazy()
Code splitting allows you to split your code into smaller chunks that can be loaded on demand, reducing the initial bundle size and improving performance.
import { Suspense, lazy } from 'react';
import { Routes, Route } from 'react-router-dom';
// Lazy load components
const Home = lazy(() => import('./pages/Home'));
const About = lazy(() => import('./pages/About'));
const Contact = lazy(() => import('./pages/Contact'));
const Dashboard = lazy(() => import('./pages/Dashboard'));
// Loading component
const PageLoader = () => (
<div className="flex items-center justify-center h-64">
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600"></div>
<span className="ml-2">Loading...</span>
</div>
);
function App() {
return (
<Suspense fallback={<PageLoader />}>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/contact" element={<Contact />} />
<Route path="/dashboard" element={<Dashboard />} />
</Routes>
</Suspense>
);
}Virtual Scrolling for Large Lists
Virtual scrolling renders only the visible items in a large list, dramatically improving performance for lists with thousands of items.
import { FixedSizeList as List } from 'react-window';
const VirtualizedList = ({ items }) => {
const Row = ({ index, style }) => (
<div style={style} className="flex items-center p-4 border-b">
<div className="w-8 h-8 bg-gray-300 rounded-full mr-4"></div>
<div>
<h3 className="font-semibold">{items[index].name}</h3>
<p className="text-gray-600">{items[index].email}</p>
</div>
</div>
);
return (
<List
height={600}
itemCount={items.length}
itemSize={80}
width="100%"
>
{Row}
</List>
);
};
// Usage
function App() {
const [users, setUsers] = useState([]);
useEffect(() => {
// Load 10,000 users
const loadUsers = async () => {
const response = await fetch('/api/users?limit=10000');
const data = await response.json();
setUsers(data);
};
loadUsers();
}, []);
return <VirtualizedList items={users} />;
}Bundle Analysis and Optimization
Analyzing your bundle size helps identify optimization opportunities and reduce the initial load time.
# Install bundle analyzer
npm install --save-dev @next/bundle-analyzer
# Analyze bundle size
npm run build
npm run analyze
# Or with webpack-bundle-analyzer
npx webpack-bundle-analyzer build/static/js/*.jsPerformance Monitoring
Monitor your React application performance using React DevTools Profiler and real user monitoring tools.
// React DevTools Profiler API
import { Profiler } from 'react';
function onRenderCallback(id, phase, actualDuration, baseDuration, startTime, commitTime) {
console.log('Profiler:', {
id,
phase,
actualDuration,
baseDuration,
startTime,
commitTime
});
}
function App() {
return (
<Profiler id="App" onRender={onRenderCallback}>
<Header />
<MainContent />
<Footer />
</Profiler>
);
}
// Performance monitoring with Web Vitals
import { getCLS, getFID, getFCP, getLCP, getTTFB } from 'web-vitals';
function sendToAnalytics(metric) {
// Send to your analytics service
console.log(metric);
}
getCLS(sendToAnalytics);
getFID(sendToAnalytics);
getFCP(sendToAnalytics);
getLCP(sendToAnalytics);
getTTFB(sendToAnalytics);Common Performance Anti-patterns
Creating objects/arrays in render methods
Inline function definitions in JSX
Overusing useEffect without proper dependencies
Not memoizing expensive calculations
Performance Best Practices
Use React.memo() for components that receive the same props frequently
Implement code splitting for route-based components
Use virtual scrolling for large lists
Optimize images with lazy loading and proper formats
Monitor and measure performance regularly
Keep your own version of these notes — editable, searchable, and organised by your stack.
Start free