React Performance Optimization Guide

Master React performance optimization techniques. Learn how to build fast, responsive applications that scale with proper memoization, code splitting, and optimization strategies.

Why Performance Optimization Matters

⚡ User Experience Impact

Performance directly affects user satisfaction and business metrics. Studies show that a 1-second delay in page load time can result in 7% reduction in conversions. For React applications, smooth interactions and fast rendering are essential for keeping users engaged.

🚀 Scalability & Maintenance

Well-optimized React applications scale better as they grow. By implementing performance best practices early, you prevent future bottlenecks and reduce the need for major refactoring. Performance optimization is not just about speed - it's about building sustainable applications.

Advertisement Space - top-performance

Google AdSense: horizontal

Performance Optimization Techniques

Rendering Optimization

React.memo and Component Memoization

Prevent unnecessary re-renders with React.memo and proper memoization strategies

❌ Unoptimized Code

1// Problem: Component re-renders unnecessarily
2const ExpensiveList = ({ items, filter, onItemClick }) => {
3 console.log('ExpensiveList rendered'); // Logs too often
4
5 const filteredItems = items.filter(item =>
6 item.category === filter
7 );
8
9 return (
10 <div>
11 {filteredItems.map(item => (
12 <div key={item.id} onClick={() => onItemClick(item)}>
13 <img src={item.image} alt={item.name} />
14 <h3>{item.name}</h3>
15 <p>{item.description}</p>
16 </div>
17 ))}
18 </div>
19 );
20};
21
22const Parent = () => {
23 const [count, setCount] = useState(0);
24 const [items] = useState(generateItems()); // Large array
25 const [filter, setFilter] = useState('all');
26
27 const handleItemClick = (item) => {
28 console.log('Item clicked:', item);
29 };
30
31 return (
32 <div>
33 <p>Count: {count}</p>
34 <button onClick={() => setCount(count + 1)}>Increment</button>
35 <ExpensiveList
36 items={items}
37 filter={filter}
38 onItemClick={handleItemClick}
39 />
40 </div>
41 );
42};

✅ Optimized Solution

1// Solution: Optimized with React.memo and memoization
2const ExpensiveList = React.memo(({ items, filter, onItemClick }) => {
3 console.log('ExpensiveList rendered');
4
5 const filteredItems = useMemo(() => {
6 console.log('Filtering items...');
7 return items.filter(item =>
8 filter === 'all' || item.category === filter
9 );
10 }, [items, filter]);
11
12 return (
13 <div>
14 {filteredItems.map(item => (
15 <ExpensiveListItem
16 key={item.id}
17 item={item}
18 onClick={onItemClick}
19 />
20 ))}
21 </div>
22 );
23});
24
25const ExpensiveListItem = React.memo(({ item, onClick }) => {
26 console.log('ExpensiveListItem rendered:', item.id);
27
28 return (
29 <div onClick={() => onClick(item)}>
30 <img src={item.image} alt={item.name} />
31 <h3>{item.name}</h3>
32 <p>{item.description}</p>
33 </div>
34 );
35});
36
37const Parent = () => {
38 const [count, setCount] = useState(0);
39 const [items] = useState(generateItems);
40 const [filter, setFilter] = useState('all');
41
42 const handleItemClick = useCallback((item) => {
43 console.log('Item clicked:', item);
44 }, []);
45
46 return (
47 <div>
48 <p>Count: {count}</p>
49 <button onClick={() => setCount(count + 1)}>Increment</button>
50 <ExpensiveList
51 items={items}
52 filter={filter}
53 onItemClick={handleItemClick}
54 />
55 </div>
56 );
57};
58
59// Advanced: Custom comparison function
60const ComplexComponent = React.memo(
61 ({ data, metadata }) => {
62 return (
63 <div>
64 <h2>{metadata.title}</h2>
65 <p>{data.content}</p>
66 </div>
67 );
68 },
69 (prevProps, nextProps) => {
70 // Only re-render if meaningful data changes
71 return (
72 prevProps.data.content === nextProps.data.content &&
73 prevProps.metadata.title === nextProps.metadata.title
74 );
75 }
76);

Performance Benefits

  • Prevents unnecessary re-renders
  • Improves performance for expensive components
  • Reduces computational overhead
  • Better user experience with smoother interactions
Bundle Optimization

Code Splitting and Lazy Loading

Reduce initial bundle size with strategic code splitting and lazy loading

❌ Unoptimized Code

1// Problem: Large bundle loading everything upfront
2import Dashboard from './components/Dashboard';
3import UserProfile from './components/UserProfile';
4import AdminPanel from './components/AdminPanel';
5import Reports from './components/Reports';
6import Settings from './components/Settings';
7
8const App = () => {
9 const [currentView, setCurrentView] = useState('dashboard');
10
11 const renderView = () => {
12 switch (currentView) {
13 case 'dashboard':
14 return <Dashboard />;
15 case 'profile':
16 return <UserProfile />;
17 case 'admin':
18 return <AdminPanel />;
19 case 'reports':
20 return <Reports />;
21 case 'settings':
22 return <Settings />;
23 default:
24 return <Dashboard />;
25 }
26 };
27
28 return (
29 <div>
30 <nav>
31 <button onClick={() => setCurrentView('dashboard')}>Dashboard</button>
32 <button onClick={() => setCurrentView('profile')}>Profile</button>
33 <button onClick={() => setCurrentView('admin')}>Admin</button>
34 <button onClick={() => setCurrentView('reports')}>Reports</button>
35 <button onClick={() => setCurrentView('settings')}>Settings</button>
36 </nav>
37 {renderView()}
38 </div>
39 );
40};

✅ Optimized Solution

1// Solution: Code splitting with React.lazy and Suspense
2import { lazy, Suspense } from 'react';
3
4// Lazy load components
5const Dashboard = lazy(() => import('./components/Dashboard'));
6const UserProfile = lazy(() => import('./components/UserProfile'));
7const AdminPanel = lazy(() => import('./components/AdminPanel'));
8const Reports = lazy(() => import('./components/Reports'));
9const Settings = lazy(() => import('./components/Settings'));
10
11// Loading component
12const LoadingSpinner = () => (
13 <div className="flex justify-center items-center p-8">
14 <div className="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-600"></div>
15 </div>
16);
17
18const App = () => {
19 const [currentView, setCurrentView] = useState('dashboard');
20
21 const renderView = () => {
22 switch (currentView) {
23 case 'dashboard':
24 return <Dashboard />;
25 case 'profile':
26 return <UserProfile />;
27 case 'admin':
28 return <AdminPanel />;
29 case 'reports':
30 return <Reports />;
31 case 'settings':
32 return <Settings />;
33 default:
34 return <Dashboard />;
35 }
36 };
37
38 return (
39 <div>
40 <nav>
41 <button onClick={() => setCurrentView('dashboard')}>Dashboard</button>
42 <button onClick={() => setCurrentView('profile')}>Profile</button>
43 <button onClick={() => setCurrentView('admin')}>Admin</button>
44 <button onClick={() => setCurrentView('reports')}>Reports</button>
45 <button onClick={() => setCurrentView('settings')}>Settings</button>
46 </nav>
47
48 <Suspense fallback={<LoadingSpinner />}>
49 {renderView()}
50 </Suspense>
51 </div>
52 );
53};
54
55// Advanced: Dynamic imports with custom loading
56const DynamicComponent = ({ componentName }) => {
57 const [Component, setComponent] = useState(null);
58 const [loading, setLoading] = useState(true);
59 const [error, setError] = useState(null);
60
61 useEffect(() => {
62 const loadComponent = async () => {
63 try {
64 setLoading(true);
65 const module = await import(`./components/${componentName}`);
66 setComponent(() => module.default);
67 } catch (err) {
68 setError(err.message);
69 } finally {
70 setLoading(false);
71 }
72 };
73
74 loadComponent();
75 }, [componentName]);
76
77 if (loading) return <LoadingSpinner />;
78 if (error) return <div>Error loading component: {error}</div>;
79 if (!Component) return null;
80
81 return <Component />;
82};
83
84// Route-based code splitting with React Router
85import { BrowserRouter, Routes, Route } from 'react-router-dom';
86
87const Home = lazy(() => import('./pages/Home'));
88const About = lazy(() => import('./pages/About'));
89const Contact = lazy(() => import('./pages/Contact'));
90
91const App = () => (
92 <BrowserRouter>
93 <Suspense fallback={<LoadingSpinner />}>
94 <Routes>
95 <Route path="/" element={<Home />} />
96 <Route path="/about" element={<About />} />
97 <Route path="/contact" element={<Contact />} />
98 </Routes>
99 </Suspense>
100 </BrowserRouter>
101);

Performance Benefits

  • Reduces initial bundle size
  • Faster page load times
  • Better performance on slow connections
  • Improved user experience
State Management

State Optimization Techniques

Optimize state management for better performance and maintainability

❌ Unoptimized Code

1// Problem: Inefficient state management
2const App = () => {
3 const [user, setUser] = useState(null);
4 const [posts, setPosts] = useState([]);
5 const [comments, setComments] = useState([]);
6 const [likes, setLikes] = useState([]);
7 const [loading, setLoading] = useState(false);
8
9 // Inefficient: All components re-render on any state change
10 const handleUserUpdate = (newUser) => {
11 setUser(newUser);
12 };
13
14 const handlePostLike = (postId) => {
15 setLikes(prev => [...prev, { postId, userId: user.id }]);
16 // This causes all components to re-render
17 };
18
19 return (
20 <div>
21 <Header user={user} onUserUpdate={handleUserUpdate} />
22 <PostList posts={posts} onLike={handlePostLike} />
23 <CommentList comments={comments} />
24 <LikesList likes={likes} />
25 </div>
26 );
27};

✅ Optimized Solution

1// Solution: Context separation and state normalization
2// Separate contexts for different concerns
3const UserContext = createContext();
4const PostsContext = createContext();
5const UIContext = createContext();
6
7const UserProvider = ({ children }) => {
8 const [user, setUser] = useState(null);
9
10 const updateUser = useCallback((newUser) => {
11 setUser(newUser);
12 }, []);
13
14 return (
15 <UserContext.Provider value={{ user, updateUser }}>
16 {children}
17 </UserContext.Provider>
18 );
19};
20
21const PostsProvider = ({ children }) => {
22 const [posts, setPosts] = useState({});
23 const [comments, setComments] = useState({});
24 const [likes, setLikes] = useState({});
25
26 const addLike = useCallback((postId, userId) => {
27 setLikes(prev => ({
28 ...prev,
29 [`${postId}-${userId}`]: { postId, userId, timestamp: Date.now() }
30 }));
31 }, []);
32
33 const addComment = useCallback((postId, comment) => {
34 setComments(prev => ({
35 ...prev,
36 [comment.id]: { ...comment, postId }
37 }));
38 }, []);
39
40 return (
41 <PostsContext.Provider value={{
42 posts,
43 comments,
44 likes,
45 addLike,
46 addComment
47 }}>
48 {children}
49 </PostsContext.Provider>
50 );
51};
52
53// Optimized component with selective subscriptions
54const PostItem = React.memo(({ postId }) => {
55 const { posts, likes, addLike } = useContext(PostsContext);
56 const { user } = useContext(UserContext);
57
58 const post = posts[postId];
59 const postLikes = Object.values(likes).filter(like => like.postId === postId);
60 const isLiked = postLikes.some(like => like.userId === user?.id);
61
62 const handleLike = useCallback(() => {
63 if (user) {
64 addLike(postId, user.id);
65 }
66 }, [postId, user, addLike]);
67
68 return (
69 <div>
70 <h3>{post.title}</h3>
71 <p>{post.content}</p>
72 <button onClick={handleLike}>
73 {isLiked ? 'Unlike' : 'Like'} ({postLikes.length})
74 </button>
75 </div>
76 );
77});
78
79// Custom hook for selective state subscription
80const useSelector = (selector) => {
81 const context = useContext(PostsContext);
82 const selectedValue = selector(context);
83
84 return useMemo(() => selectedValue, [selectedValue]);
85};
86
87// Usage with selective subscription
88const PostStats = ({ postId }) => {
89 const likesCount = useSelector(state =>
90 Object.values(state.likes).filter(like => like.postId === postId).length
91 );
92
93 return <span>Likes: {likesCount}</span>;
94};

Performance Benefits

  • Reduces unnecessary re-renders
  • Better state organization
  • Improved scalability
  • Easier debugging and maintenance
List Optimization

Virtual Scrolling and List Performance

Optimize large lists with virtual scrolling and efficient rendering

❌ Unoptimized Code

1// Problem: Rendering large lists inefficiently
2const LargeList = ({ items }) => {
3 const [filter, setFilter] = useState('');
4
5 const filteredItems = items.filter(item =>
6 item.name.toLowerCase().includes(filter.toLowerCase())
7 );
8
9 return (
10 <div>
11 <input
12 type="text"
13 placeholder="Filter items..."
14 value={filter}
15 onChange={(e) => setFilter(e.target.value)}
16 />
17
18 <div style={{ height: '400px', overflow: 'auto' }}>
19 {filteredItems.map(item => (
20 <div key={item.id} style={{ height: '50px', padding: '10px' }}>
21 <img src={item.image} alt={item.name} />
22 <h3>{item.name}</h3>
23 <p>{item.description}</p>
24 </div>
25 ))}
26 </div>
27 </div>
28 );
29};

✅ Optimized Solution

1// Solution: Virtual scrolling with react-window
2import { FixedSizeList as List } from 'react-window';
3
4const VirtualizedList = ({ items }) => {
5 const [filter, setFilter] = useState('');
6
7 const filteredItems = useMemo(() =>
8 items.filter(item =>
9 item.name.toLowerCase().includes(filter.toLowerCase())
10 ), [items, filter]
11 );
12
13 const ListItem = ({ index, style }) => {
14 const item = filteredItems[index];
15
16 return (
17 <div style={style}>
18 <div style={{ height: '50px', padding: '10px' }}>
19 <img src={item.image} alt={item.name} />
20 <h3>{item.name}</h3>
21 <p>{item.description}</p>
22 </div>
23 </div>
24 );
25 };
26
27 return (
28 <div>
29 <input
30 type="text"
31 placeholder="Filter items..."
32 value={filter}
33 onChange={(e) => setFilter(e.target.value)}
34 />
35
36 <List
37 height={400}
38 itemCount={filteredItems.length}
39 itemSize={70}
40 itemData={filteredItems}
41 >
42 {ListItem}
43 </List>
44 </div>
45 );
46};
47
48// Advanced: Custom virtual scrolling hook
49const useVirtualScroll = ({ items, itemHeight, containerHeight }) => {
50 const [scrollTop, setScrollTop] = useState(0);
51 const [containerRef, setContainerRef] = useState(null);
52
53 const startIndex = Math.floor(scrollTop / itemHeight);
54 const endIndex = Math.min(
55 startIndex + Math.ceil(containerHeight / itemHeight) + 1,
56 items.length
57 );
58
59 const visibleItems = items.slice(startIndex, endIndex);
60 const totalHeight = items.length * itemHeight;
61 const offsetY = startIndex * itemHeight;
62
63 const handleScroll = useCallback((e) => {
64 setScrollTop(e.target.scrollTop);
65 }, []);
66
67 return {
68 containerRef: setContainerRef,
69 visibleItems,
70 totalHeight,
71 offsetY,
72 onScroll: handleScroll
73 };
74};
75
76// Usage with custom hook
77const CustomVirtualList = ({ items }) => {
78 const {
79 containerRef,
80 visibleItems,
81 totalHeight,
82 offsetY,
83 onScroll
84 } = useVirtualScroll({
85 items,
86 itemHeight: 70,
87 containerHeight: 400
88 });
89
90 return (
91 <div
92 ref={containerRef}
93 style={{ height: '400px', overflow: 'auto' }}
94 onScroll={onScroll}
95 >
96 <div style={{ height: totalHeight, position: 'relative' }}>
97 <div style={{ transform: `translateY(${offsetY}px)` }}>
98 {visibleItems.map((item, index) => (
99 <div key={item.id} style={{ height: '70px', padding: '10px' }}>
100 <h3>{item.name}</h3>
101 <p>{item.description}</p>
102 </div>
103 ))}
104 </div>
105 </div>
106 </div>
107 );
108};
109
110// Intersection Observer for infinite scrolling
111const useInfiniteScroll = (loadMore, hasMore) => {
112 const [isFetching, setIsFetching] = useState(false);
113 const loadMoreRef = useRef(null);
114
115 useEffect(() => {
116 if (!hasMore || isFetching) return;
117
118 const observer = new IntersectionObserver(
119 ([entry]) => {
120 if (entry.isIntersecting) {
121 setIsFetching(true);
122 loadMore().finally(() => setIsFetching(false));
123 }
124 },
125 { threshold: 1.0 }
126 );
127
128 if (loadMoreRef.current) {
129 observer.observe(loadMoreRef.current);
130 }
131
132 return () => observer.disconnect();
133 }, [loadMore, hasMore, isFetching]);
134
135 return { loadMoreRef, isFetching };
136};

Performance Benefits

  • Handles thousands of items efficiently
  • Reduces memory usage
  • Improves scroll performance
  • Better user experience for large datasets
Image Optimization

Image Loading and Optimization

Optimize image loading with lazy loading, progressive loading, and responsive images

❌ Unoptimized Code

1// Problem: Loading all images at once
2const Gallery = ({ images }) => {
3 return (
4 <div className="gallery">
5 {images.map(image => (
6 <div key={image.id} className="image-item">
7 <img
8 src={image.highResUrl}
9 alt={image.title}
10 style={{ width: '300px', height: '200px' }}
11 />
12 <h3>{image.title}</h3>
13 </div>
14 ))}
15 </div>
16 );
17};

✅ Optimized Solution

1// Solution: Optimized image loading
2import { useState, useEffect, useRef } from 'react';
3
4// Lazy loading image component
5const LazyImage = ({ src, alt, placeholder, className }) => {
6 const [isLoaded, setIsLoaded] = useState(false);
7 const [isInView, setIsInView] = useState(false);
8 const imgRef = useRef(null);
9
10 useEffect(() => {
11 const observer = new IntersectionObserver(
12 ([entry]) => {
13 if (entry.isIntersecting) {
14 setIsInView(true);
15 observer.disconnect();
16 }
17 },
18 { threshold: 0.1 }
19 );
20
21 if (imgRef.current) {
22 observer.observe(imgRef.current);
23 }
24
25 return () => observer.disconnect();
26 }, []);
27
28 return (
29 <div ref={imgRef} className={className}>
30 {isInView && (
31 <img
32 src={src}
33 alt={alt}
34 onLoad={() => setIsLoaded(true)}
35 style={{
36 opacity: isLoaded ? 1 : 0,
37 transition: 'opacity 0.3s ease'
38 }}
39 />
40 )}
41 {!isLoaded && isInView && (
42 <div className="placeholder">
43 {placeholder || 'Loading...'}
44 </div>
45 )}
46 </div>
47 );
48};
49
50// Progressive image loading
51const ProgressiveImage = ({ src, placeholderSrc, alt, className }) => {
52 const [currentSrc, setCurrentSrc] = useState(placeholderSrc);
53 const [isLoaded, setIsLoaded] = useState(false);
54
55 useEffect(() => {
56 const img = new Image();
57 img.onload = () => {
58 setCurrentSrc(src);
59 setIsLoaded(true);
60 };
61 img.src = src;
62 }, [src]);
63
64 return (
65 <img
66 src={currentSrc}
67 alt={alt}
68 className={className}
69 style={{
70 filter: isLoaded ? 'none' : 'blur(5px)',
71 transition: 'filter 0.3s ease'
72 }}
73 />
74 );
75};
76
77// Responsive image component
78const ResponsiveImage = ({ src, alt, sizes, className }) => {
79 const generateSrcSet = (baseSrc) => {
80 const sizes = [400, 800, 1200, 1600];
81 return sizes.map(size =>
82 `${baseSrc}?w=${size} ${size}w`
83 ).join(', ');
84 };
85
86 return (
87 <img
88 src={src}
89 srcSet={generateSrcSet(src)}
90 sizes={sizes}
91 alt={alt}
92 className={className}
93 loading="lazy"
94 />
95 );
96};
97
98// Optimized gallery component
99const OptimizedGallery = ({ images }) => {
100 const [loadedImages, setLoadedImages] = useState(new Set());
101
102 const handleImageLoad = (imageId) => {
103 setLoadedImages(prev => new Set(prev).add(imageId));
104 };
105
106 return (
107 <div className="gallery">
108 {images.map(image => (
109 <div key={image.id} className="image-item">
110 <ProgressiveImage
111 src={image.highResUrl}
112 placeholderSrc={image.thumbnailUrl}
113 alt={image.title}
114 className="gallery-image"
115 />
116 <h3>{image.title}</h3>
117 </div>
118 ))}
119 </div>
120 );
121};
122
123// Image preloading hook
124const useImagePreload = (urls) => {
125 const [loadedImages, setLoadedImages] = useState(new Set());
126
127 useEffect(() => {
128 const preloadImages = urls.map(url => {
129 const img = new Image();
130 img.onload = () => {
131 setLoadedImages(prev => new Set(prev).add(url));
132 };
133 img.src = url;
134 return img;
135 });
136
137 return () => {
138 preloadImages.forEach(img => {
139 img.onload = null;
140 img.src = '';
141 });
142 };
143 }, [urls]);
144
145 return loadedImages;
146};

Performance Benefits

  • Faster initial page load
  • Reduced bandwidth usage
  • Better user experience
  • Improved performance scores

Advertisement Space - bottom-performance

Google AdSense: horizontal