Performance Optimization in React
Performance optimization ensures fast, responsive React applications. This article covers optimization techniques and best practices.
Introduction
Performance optimization provides:
- Faster rendering
- Reduced bundle size
- Better user experience
- Lower memory usage
- Improved SEO
Understanding optimization helps you:
- Identify performance bottlenecks
- Optimize component rendering
- Reduce bundle size
- Improve load times
- Monitor performance
Memoization
React.memo
import { memo } from 'react';
// โ
Good: Memoize expensive component
const UserCard = memo(function UserCard({ user, onEdit }) {
console.log('UserCard rendered');
return (
<div>
<h3>{user.name}</h3>
<p>{user.email}</p>
<button onClick={() => onEdit(user.id)}>Edit</button>
</div>
);
});
// Usage
function UserList({ users }) {
const [editingId, setEditingId] = React.useState(null);
return (
<div>
{users.map(user => (
<UserCard
key={user.id}
user={user}
onEdit={setEditingId}
/>
))}
</div>
);
}
// โ
Good: Custom comparison
const UserCard = memo(
function UserCard({ user, onEdit }) {
return <div>{user.name}</div>;
},
(prevProps, nextProps) => {
return prevProps.user.id === nextProps.user.id;
}
);
// โ
Good: Memoize with useCallback
function Parent() {
const [count, setCount] = React.useState(0);
const handleEdit = React.useCallback((userId) => {
console.log('Edit user:', userId);
}, []);
return (
<div>
<button onClick={() => setCount(count + 1)}>Count: {count}</button>
<UserCard user={{ id: 1, name: 'John' }} onEdit={handleEdit} />
</div>
);
}
useMemo
import { useMemo } from 'react';
// โ
Good: Memoize expensive calculations
function DataProcessor({ data }) {
const processedData = useMemo(() => {
console.log('Processing data...');
return data.map(item => ({
...item,
processed: item.value * 2
}));
}, [data]);
return (
<div>
{processedData.map(item => (
<p key={item.id}>{item.processed}</p>
))}
</div>
);
}
// โ
Good: Memoize filtered lists
function FilteredList({ items, filter }) {
const filteredItems = useMemo(() => {
return items.filter(item => item.name.includes(filter));
}, [items, filter]);
return (
<ul>
{filteredItems.map(item => (
<li key={item.id}>{item.name}</li>
))}
</ul>
);
}
// โ
Good: Memoize object creation
function Component() {
const [count, setCount] = React.useState(0);
const config = useMemo(() => ({
theme: 'dark',
size: 'large'
}), []);
return (
<div>
<button onClick={() => setCount(count + 1)}>Count: {count}</button>
<ChildComponent config={config} />
</div>
);
}
useCallback
import { useCallback } from 'react';
// โ
Good: Memoize callback
function SearchUsers() {
const [query, setQuery] = React.useState('');
const handleSearch = useCallback((searchTerm) => {
console.log('Searching for:', searchTerm);
// Expensive search operation
}, []);
return (
<div>
<input
value={query}
onChange={(e) => setQuery(e.target.value)}
onBlur={() => handleSearch(query)}
/>
</div>
);
}
// โ
Good: useCallback with dependencies
function Parent() {
const [userId, setUserId] = React.useState(1);
const fetchUser = useCallback(async () => {
const response = await fetch(`/api/users/${userId}`);
return response.json();
}, [userId]);
return (
<div>
<button onClick={() => setUserId(userId + 1)}>Next User</button>
<UserProfile fetchUser={fetchUser} />
</div>
);
}
// โ
Good: Avoid unnecessary callbacks
function Component() {
// โ Bad: Creates new function on every render
const handleClick = () => {
console.log('Clicked');
};
// โ
Good: Memoize callback
const handleClick = useCallback(() => {
console.log('Clicked');
}, []);
return <button onClick={handleClick}>Click</button>;
}
Code Splitting
Lazy Loading Components
import { lazy, Suspense } from 'react';
// โ
Good: Lazy load components
const Dashboard = lazy(() => import('./pages/Dashboard'));
const AdminPanel = lazy(() => import('./pages/AdminPanel'));
const Settings = lazy(() => import('./pages/Settings'));
function App() {
return (
<Suspense fallback={<p>Loading...</p>}>
<Routes>
<Route path="/dashboard" element={<Dashboard />} />
<Route path="/admin" element={<AdminPanel />} />
<Route path="/settings" element={<Settings />} />
</Routes>
</Suspense>
);
}
// โ
Good: Custom loading component
function LoadingSpinner() {
return (
<div className="spinner">
<p>Loading...</p>
</div>
);
}
function App() {
return (
<Suspense fallback={<LoadingSpinner />}>
<Routes>
<Route path="/dashboard" element={<Dashboard />} />
</Routes>
</Suspense>
);
}
// โ
Good: Lazy load with error boundary
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error) {
return { hasError: true };
}
render() {
if (this.state.hasError) {
return <h1>Failed to load component</h1>;
}
return this.props.children;
}
}
function App() {
return (
<ErrorBoundary>
<Suspense fallback={<LoadingSpinner />}>
<Routes>
<Route path="/dashboard" element={<Dashboard />} />
</Routes>
</Suspense>
</ErrorBoundary>
);
}
Virtual Scrolling
Virtualized Lists
import { FixedSizeList } from 'react-window';
// โ
Good: Virtual scrolling for large lists
function LargeList({ items }) {
const Row = ({ index, style }) => (
<div style={style}>
{items[index].name}
</div>
);
return (
<FixedSizeList
height={600}
itemCount={items.length}
itemSize={35}
width="100%"
>
{Row}
</FixedSizeList>
);
}
// โ
Good: Dynamic size virtual list
import { VariableSizeList } from 'react-window';
function DynamicList({ items }) {
const listRef = React.useRef();
const Row = ({ index, style }) => (
<div style={style}>
{items[index].name}
</div>
);
return (
<VariableSizeList
height={600}
itemCount={items.length}
itemSize={() => 35}
width="100%"
ref={listRef}
>
{Row}
</VariableSizeList>
);
}
Image Optimization
Lazy Loading Images
// โ
Good: Lazy load images
function Image({ src, alt }) {
return (
<img
src={src}
alt={alt}
loading="lazy"
/>
);
}
// โ
Good: Responsive images
function ResponsiveImage({ src, alt }) {
return (
<picture>
<source media="(max-width: 600px)" srcSet={`${src}-small.jpg`} />
<source media="(max-width: 1200px)" srcSet={`${src}-medium.jpg`} />
<img src={`${src}-large.jpg`} alt={alt} />
</picture>
);
}
// โ
Good: Image with placeholder
function ImageWithPlaceholder({ src, alt, placeholder }) {
const [loaded, setLoaded] = React.useState(false);
return (
<div>
{!loaded && <img src={placeholder} alt="placeholder" />}
<img
src={src}
alt={alt}
onLoad={() => setLoaded(true)}
style={{ display: loaded ? 'block' : 'none' }}
/>
</div>
);
}
// โ
Good: WebP with fallback
function ModernImage({ src, alt }) {
return (
<picture>
<source srcSet={`${src}.webp`} type="image/webp" />
<img src={`${src}.jpg`} alt={alt} />
</picture>
);
}
Bundle Analysis
Analyzing Bundle Size
// โ
Good: Dynamic imports
function App() {
const [showAdmin, setShowAdmin] = React.useState(false);
const loadAdmin = async () => {
const AdminModule = await import('./pages/AdminPanel');
setShowAdmin(true);
};
return (
<div>
<button onClick={loadAdmin}>Load Admin</button>
{showAdmin && <AdminPanel />}
</div>
);
}
// โ
Good: Tree shaking
// In package.json
// "sideEffects": false
// โ
Good: Remove unused dependencies
// Use webpack-bundle-analyzer
// npm install --save-dev webpack-bundle-analyzer
Performance Monitoring
React Profiler
import { Profiler } from 'react';
// โ
Good: Profile component
function onRenderCallback(
id,
phase,
actualDuration,
baseDuration,
startTime,
commitTime
) {
console.log(`${id} (${phase}) took ${actualDuration}ms`);
}
function App() {
return (
<Profiler id="App" onRender={onRenderCallback}>
<Dashboard />
</Profiler>
);
}
// โ
Good: Performance monitoring
function usePerformanceMonitor(componentName) {
React.useEffect(() => {
const startTime = performance.now();
return () => {
const endTime = performance.now();
console.log(`${componentName} took ${endTime - startTime}ms`);
};
}, [componentName]);
}
function Component() {
usePerformanceMonitor('Component');
return <div>Component</div>;
}
Best Practices
-
Memoize expensive components:
// โ Good: Memoize const UserCard = memo(UserCardComponent); // โ Bad: No memoization function UserCard() { } -
Use lazy loading for routes:
// โ Good: Lazy load const Dashboard = lazy(() => import('./Dashboard')); // โ Bad: Import everything import Dashboard from './Dashboard'; -
Optimize images:
// โ Good: Lazy load images <img loading="lazy" src={src} /> // โ Bad: No optimization <img src={src} />
Summary
Performance optimization is essential. Key takeaways:
- Use React.memo for expensive components
- Use useMemo for expensive calculations
- Use useCallback for callbacks
- Lazy load routes and components
- Optimize images
- Monitor performance
- Analyze bundle size
- Use virtual scrolling for large lists
Related Resources
Next Steps
- Learn about Vue.js
- Explore Angular
- Study Backend Development
- Practice optimization techniques
- Monitor and improve application performance
Comments