Master advanced techniques for optimizing React applications and delivering exceptional user experiences. Learn how to identify and resolve performance bottlenecks. 🚀
Code Splitting 📦
// Dynamic Imports
import { lazy, Suspense } from 'react';
const HeavyComponent = lazy(() =>
import('./HeavyComponent')
);
function App() {
return (
<Suspense fallback={<Loading />}>
<HeavyComponent />
</Suspense>
);
}
// Route-based Code Splitting
import {
BrowserRouter,
Routes,
Route
} from 'react-router-dom';
const Home = lazy(() => import('./pages/Home'));
const Dashboard = lazy(() => import('./pages/Dashboard'));
const Profile = lazy(() => import('./pages/Profile'));
function App() {
return (
<BrowserRouter>
<Suspense fallback={<Loading />}>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/dashboard" element={<Dashboard />} />
<Route path="/profile" element={<Profile />} />
</Routes>
</Suspense>
</BrowserRouter>
);
}
// Component-based Code Splitting
function MyComponent() {
const [showModal, setShowModal] = useState(false);
const Modal = useMemo(
() => lazy(() => import('./Modal')),
[]
);
return (
<div>
<button onClick={() => setShowModal(true)}>
Open Modal
</button>
{showModal && (
<Suspense fallback={<Loading />}>
<Modal onClose={() => setShowModal(false)} />
</Suspense>
)}
</div>
);
}
Memoization Techniques 🎯
// useMemo for Expensive Calculations
function ExpensiveComponent({ data }) {
const processedData = useMemo(() => {
return data.map(item => ({
...item,
value: expensiveCalculation(item)
}));
}, [data]);
return (
<ul>
{processedData.map(item => (
<li key={item.id}>{item.value}</li>
))}
</ul>
);
}
// useCallback for Event Handlers
function ParentComponent() {
const [count, setCount] = useState(0);
const handleClick = useCallback(() => {
setCount(c => c + 1);
}, []);
return <ChildComponent onClick={handleClick} />;
}
// React.memo for Component Memoization
const MemoizedComponent = React.memo(
function Component({ value, onChange }) {
return (
<div>
<input
value={value}
onChange={e => onChange(e.target.value)}
/>
</div>
);
},
(prevProps, nextProps) => {
return prevProps.value === nextProps.value;
}
);
// Custom Hook with Memoization
function useSearch(items, searchTerm) {
const filteredItems = useMemo(() => {
return items.filter(item =>
item.name.toLowerCase().includes(
searchTerm.toLowerCase()
)
);
}, [items, searchTerm]);
const stats = useMemo(() => {
return {
total: items.length,
filtered: filteredItems.length,
percentage: (
(filteredItems.length / items.length) * 100
).toFixed(2)
};
}, [items.length, filteredItems.length]);
return { filteredItems, stats };
}
Virtualization 📜
// Virtual List Implementation
interface Item {
id: string;
content: string;
}
interface VirtualListProps {
items: Item[];
itemHeight: number;
windowHeight: number;
}
function VirtualList({
items,
itemHeight,
windowHeight
}: VirtualListProps) {
const [scrollTop, setScrollTop] = useState(0);
const startIndex = Math.floor(scrollTop / itemHeight);
const endIndex = Math.min(
startIndex + Math.ceil(windowHeight / itemHeight),
items.length
);
const visibleItems = items.slice(startIndex, endIndex);
const totalHeight = items.length * itemHeight;
const offsetY = startIndex * itemHeight;
return (
<div
style={{ height: windowHeight, overflow: 'auto' }}
onScroll={e => {
setScrollTop(e.currentTarget.scrollTop);
}}
>
<div style={{ height: totalHeight }}>
<div style={{
transform: `translateY(${offsetY}px)`
}}>
{visibleItems.map(item => (
<div
key={item.id}
style={{ height: itemHeight }}
>
{item.content}
</div>
))}
</div>
</div>
</div>
);
}
// Virtual Grid Implementation
interface GridItem {
id: string;
content: string;
}
interface VirtualGridProps {
items: GridItem[];
columnCount: number;
columnWidth: number;
rowHeight: number;
windowWidth: number;
windowHeight: number;
}
function VirtualGrid({
items,
columnCount,
columnWidth,
rowHeight,
windowWidth,
windowHeight
}: VirtualGridProps) {
const [scrollPosition, setScrollPosition] = useState({
top: 0,
left: 0
});
const rowCount = Math.ceil(items.length / columnCount);
const startRow = Math.floor(
scrollPosition.top / rowHeight
);
const endRow = Math.min(
startRow + Math.ceil(windowHeight / rowHeight),
rowCount
);
const startCol = Math.floor(
scrollPosition.left / columnWidth
);
const endCol = Math.min(
startCol + Math.ceil(windowWidth / columnWidth),
columnCount
);
const visibleItems = [];
for (let row = startRow; row < endRow; row++) {
for (let col = startCol; col < endCol; col++) {
const index = row * columnCount + col;
if (index < items.length) {
visibleItems.push({
...items[index],
row,
col
});
}
}
}
return (
<div
style={{
width: windowWidth,
height: windowHeight,
overflow: 'auto'
}}
onScroll={e => {
setScrollPosition({
top: e.currentTarget.scrollTop,
left: e.currentTarget.scrollLeft
});
}}
>
<div style={{
height: rowCount * rowHeight,
width: columnCount * columnWidth,
position: 'relative'
}}>
{visibleItems.map(item => (
<div
key={item.id}
style={{
position: 'absolute',
top: item.row * rowHeight,
left: item.col * columnWidth,
width: columnWidth,
height: rowHeight
}}
>
{item.content}
</div>
))}
</div>
</div>
);
}
State Management Optimization 🔄
// Context Splitting
const UserContext = createContext<User | null>(null);
const ThemeContext = createContext<Theme>('light');
const SettingsContext = createContext<Settings | null>(null);
function App() {
return (
<ThemeContext.Provider value="light">
<UserContext.Provider value={user}>
<SettingsContext.Provider value={settings}>
<AppContent />
</SettingsContext.Provider>
</UserContext.Provider>
</ThemeContext.Provider>
);
}
// State Colocating
function ParentComponent() {
const [selectedId, setSelectedId] = useState(null);
return (
<div>
<List onSelect={setSelectedId} />
<Details selectedId={selectedId} />
</div>
);
}
// State Machine Pattern
import { createMachine, assign } from 'xstate';
import { useMachine } from '@xstate/react';
const fetchMachine = createMachine({
id: 'fetch',
initial: 'idle',
context: {
data: null,
error: null
},
states: {
idle: {
on: { FETCH: 'loading' }
},
loading: {
invoke: {
src: 'fetchData',
onDone: {
target: 'success',
actions: assign({
data: (_, event) => event.data
})
},
onError: {
target: 'failure',
actions: assign({
error: (_, event) => event.data
})
}
}
},
success: {
on: { FETCH: 'loading' }
},
failure: {
on: { RETRY: 'loading' }
}
}
});
function DataComponent() {
const [state, send] = useMachine(fetchMachine, {
services: {
fetchData: async () => {
const response = await fetch('/api/data');
return response.json();
}
}
});
if (state.matches('loading')) {
return <Loading />;
}
if (state.matches('failure')) {
return (
<Error
message={state.context.error}
onRetry={() => send('RETRY')}
/>
);
}
return (
<div>
<pre>{JSON.stringify(state.context.data)}</pre>
<button onClick={() => send('FETCH')}>
Refresh
</button>
</div>
);
}
Performance Monitoring 📊
// Custom Performance Hook
function usePerformanceMonitor(componentName: string) {
useEffect(() => {
const startTime = performance.now();
return () => {
const endTime = performance.now();
const duration = endTime - startTime;
console.log(
`${componentName} rendered in ${duration}ms`
);
// Send to analytics
analytics.track('component_render', {
component: componentName,
duration
});
};
});
}
// Render Count Hook
function useRenderCount() {
const count = useRef(0);
useEffect(() => {
count.current++;
});
return count.current;
}
// Performance Metrics Component
function PerformanceMetrics() {
const [metrics, setMetrics] = useState({
fcp: 0,
lcp: 0,
fid: 0,
cls: 0
});
useEffect(() => {
// First Contentful Paint
new PerformanceObserver((entryList) => {
const entries = entryList.getEntries();
const fcp = entries[entries.length - 1];
setMetrics(m => ({
...m,
fcp: fcp.startTime
}));
}).observe({ entryTypes: ['paint'] });
// Largest Contentful Paint
new PerformanceObserver((entryList) => {
const entries = entryList.getEntries();
const lcp = entries[entries.length - 1];
setMetrics(m => ({
...m,
lcp: lcp.startTime
}));
}).observe({ entryTypes: ['largest-contentful-paint'] });
// First Input Delay
new PerformanceObserver((entryList) => {
const entries = entryList.getEntries();
const fid = entries[entries.length - 1];
setMetrics(m => ({
...m,
fid: fid.processingStart - fid.startTime
}));
}).observe({ entryTypes: ['first-input'] });
// Cumulative Layout Shift
new PerformanceObserver((entryList) => {
const entries = entryList.getEntries();
let cls = 0;
entries.forEach(entry => {
if (!entry.hadRecentInput) {
cls += entry.value;
}
});
setMetrics(m => ({ ...m, cls }));
}).observe({ entryTypes: ['layout-shift'] });
}, []);
return (
<div>
<h2>Performance Metrics</h2>
<ul>
<li>First Contentful Paint: {metrics.fcp}ms</li>
<li>Largest Contentful Paint: {metrics.lcp}ms</li>
<li>First Input Delay: {metrics.fid}ms</li>
<li>Cumulative Layout Shift: {metrics.cls}</li>
</ul>
</div>
);
}
Best Practices 📝
- Use code splitting
- Implement proper memoization
- Use virtualization for long lists
- Optimize state management
- Monitor performance
- Use proper bundling
- Implement proper caching
- Use proper rendering strategies
- Optimize images and assets
- Follow React performance patterns
Additional Resources
React performance optimization is crucial for delivering great user experiences. This guide covers essential techniques and best practices for optimizing React applications.