React Performance Optimization Techniques
January 5, 2025
4 min read
Jason Sy
ReactPerformanceOptimizationJavaScript
React Performance Optimization Techniques
Performance is crucial for user experience. Here are essential techniques to optimize your React applications.
1. Use React.memo for Component Memoization
Prevent unnecessary re-renders:
import { memo } from 'react';
interface UserCardProps {
name: string;
email: string;
}
const UserCard = memo(({ name, email }: UserCardProps) => {
console.log('UserCard rendered');
return (
<div>
<h3>{name}</h3>
<p>{email}</p>
</div>
);
});
2. Optimize useMemo and useCallback
Cache expensive computations and functions:
import { useMemo, useCallback } from 'react';
function DataList({ items, filter }) {
// Memoize expensive filtering operation
const filteredItems = useMemo(() => {
return items.filter(item => item.includes(filter));
}, [items, filter]);
// Memoize callback to prevent child re-renders
const handleClick = useCallback((id: string) => {
console.log('Clicked:', id);
}, []);
return (
<ul>
{filteredItems.map(item => (
<ListItem key={item.id} onClick={handleClick} />
))}
</ul>
);
}
3. Code Splitting with Lazy Loading
Split your bundle for faster initial loads:
import { lazy, Suspense } from 'react';
const HeavyChart = lazy(() => import('@/components/HeavyChart'));
const AdminPanel = lazy(() => import('@/components/AdminPanel'));
function Dashboard() {
return (
<div>
<Suspense fallback={<div>Loading chart...</div>}>
<HeavyChart />
</Suspense>
<Suspense fallback={<div>Loading admin panel...</div>}>
<AdminPanel />
</Suspense>
</div>
);
}
4. Virtual Scrolling for Large Lists
Use virtualization for rendering large datasets:
import { useVirtualizer } from '@tanstack/react-virtual';
function VirtualList({ items }) {
const parentRef = useRef<HTMLDivElement>(null);
const virtualizer = useVirtualizer({
count: items.length,
getScrollElement: () => parentRef.current,
estimateSize: () => 50,
});
return (
<div ref={parentRef} style={{ height: '400px', overflow: 'auto' }}>
<div style={{ height: `${virtualizer.getTotalSize()}px` }}>
{virtualizer.getVirtualItems().map(virtualRow => (
<div
key={virtualRow.index}
style={{
position: 'absolute',
top: 0,
transform: `translateY(${virtualRow.start}px)`,
}}
>
{items[virtualRow.index].name}
</div>
))}
</div>
</div>
);
}
5. Optimize Context Usage
Prevent context re-renders:
import { createContext, useContext, useMemo } from 'react';
const UserContext = createContext(null);
function UserProvider({ children, user }) {
// Memoize context value
const value = useMemo(() => ({
user,
isLoggedIn: !!user,
}), [user]);
return (
<UserContext.Provider value={value}>
{children}
</UserContext.Provider>
);
}
6. Use Transition for Non-Urgent Updates
Prioritize urgent updates:
import { useState, useTransition } from 'react';
function SearchResults() {
const [query, setQuery] = useState('');
const [results, setResults] = useState([]);
const [isPending, startTransition] = useTransition();
const handleSearch = (value: string) => {
// Urgent: update input immediately
setQuery(value);
// Non-urgent: search can wait
startTransition(() => {
const filtered = performExpensiveSearch(value);
setResults(filtered);
});
};
return (
<div>
<input value={query} onChange={e => handleSearch(e.target.value)} />
{isPending ? <Spinner /> : <ResultsList items={results} />}
</div>
);
}
7. Debounce Expensive Operations
Limit how often expensive functions run:
import { useState, useEffect } from 'react';
function useDebounce<T>(value: T, delay: number): T {
const [debouncedValue, setDebouncedValue] = useState(value);
useEffect(() => {
const handler = setTimeout(() => {
setDebouncedValue(value);
}, delay);
return () => {
clearTimeout(handler);
};
}, [value, delay]);
return debouncedValue;
}
// Usage
function SearchComponent() {
const [searchTerm, setSearchTerm] = useState('');
const debouncedSearchTerm = useDebounce(searchTerm, 500);
useEffect(() => {
if (debouncedSearchTerm) {
// API call only happens after user stops typing for 500ms
fetchSearchResults(debouncedSearchTerm);
}
}, [debouncedSearchTerm]);
}
8. Optimize Image Loading
Use proper image optimization techniques:
import Image from 'next/image';
function OptimizedImage() {
return (
<Image
src="/large-image.jpg"
alt="Description"
width={800}
height={600}
priority // For above-the-fold images
placeholder="blur"
blurDataURL="data:image/jpeg;base64,..."
/>
);
}
Performance Monitoring
Monitor your app's performance:
import { useEffect } from 'react';
function PerformanceMonitor() {
useEffect(() => {
// Web Vitals
if (typeof window !== 'undefined' && 'performance' in window) {
const observer = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
console.log('Performance metric:', entry);
}
});
observer.observe({ entryTypes: ['navigation', 'paint', 'measure'] });
return () => observer.disconnect();
}
}, []);
}
Best Practices Checklist
- ✅ Profile before optimizing
- ✅ Use React DevTools Profiler
- ✅ Implement code splitting
- ✅ Memoize expensive computations
- ✅ Virtualize long lists
- ✅ Optimize images and assets
- ✅ Use proper caching strategies
- ✅ Monitor performance metrics
Conclusion
React performance optimization is about making smart choices:
- Measure first: Use profiling tools to identify bottlenecks
- Optimize strategically: Focus on user-facing performance issues
- Test thoroughly: Ensure optimizations don't break functionality
Remember: premature optimization is the root of all evil. Always measure before optimizing!