Skip to main content

Performance

React performance optimization techniques, profiling, memoization, and best practices for building fast applications.

Performance Optimization Techniques

React.memo

import React from 'react';

// Basic memoization
const ExpensiveComponent = React.memo(function ExpensiveComponent({ data }) {
console.log('ExpensiveComponent rendered');

return (
<div>
{data.map(item => (
<div key={item.id}>{item.name}</div>
))}
</div>
);
});

// Custom comparison function
const OptimizedComponent = React.memo(
function OptimizedComponent({ user, settings }) {
return (
<div>
<h1>{user.name}</h1>
<p>Theme: {settings.theme}</p>
</div>
);
},
(prevProps, nextProps) => {
// Only re-render if user ID or theme changes
return (
prevProps.user.id === nextProps.user.id &&
prevProps.settings.theme === nextProps.settings.theme
);
}
);

useMemo Hook

import { useMemo, useState } from 'react';

function ExpensiveCalculation({ items, multiplier, filter }) {
const [sortOrder, setSortOrder] = useState('asc');

// Memoize expensive filtering and sorting
const processedItems = useMemo(() => {
console.log('Processing items...');

// Filter items
const filtered = items.filter(item =>
item.name.toLowerCase().includes(filter.toLowerCase())
);

// Sort items
const sorted = filtered.sort((a, b) => {
const comparison = a.name.localeCompare(b.name);
return sortOrder === 'asc' ? comparison : -comparison;
});

// Apply multiplier
return sorted.map(item => ({
...item,
value: item.value * multiplier,
}));
}, [items, multiplier, filter, sortOrder]);

return (
<div>
<select value={sortOrder} onChange={e => setSortOrder(e.target.value)}>
<option value="asc">Ascending</option>
<option value="desc">Descending</option>
</select>

<ul>
{processedItems.map(item => (
<li key={item.id}>
{item.name}: {item.value}
</li>
))}
</ul>
</div>
);
}

useCallback Hook

import { useCallback, useState, memo } from 'react';

// Child component that should not re-render unnecessarily
const ListItem = memo(function ListItem({ item, onEdit, onDelete }) {
console.log('ListItem rendered for:', item.name);

return (
<li>
<span>{item.name}</span>
<button onClick={() => onEdit(item.id)}>Edit</button>
<button onClick={() => onDelete(item.id)}>Delete</button>
</li>
);
});

function ItemList({ items }) {
const [editMode, setEditMode] = useState(null);
const [filter, setFilter] = useState('');

// Memoize callbacks to prevent child re-renders
const handleEdit = useCallback(id => {
setEditMode(id);
}, []);

const handleDelete = useCallback(id => {
// Delete logic here
console.log('Deleting item:', id);
}, []);

const filteredItems = items.filter(item =>
item.name.toLowerCase().includes(filter.toLowerCase())
);

return (
<div>
<input
value={filter}
onChange={e => setFilter(e.target.value)}
placeholder="Filter items..."
/>

<ul>
{filteredItems.map(item => (
<ListItem
key={item.id}
item={item}
onEdit={handleEdit}
onDelete={handleDelete}
/>
))}
</ul>
</div>
);
}

Code Splitting

Lazy Loading Components

import { lazy, Suspense } from 'react';

// Lazy load components
const Dashboard = lazy(() => import('./Dashboard'));
const Profile = lazy(() => import('./Profile'));
const Settings = lazy(() => import('./Settings'));

// Loading component
function LoadingSpinner() {
return (
<div className="loading">
<div className="spinner"></div>
<p>Loading...</p>
</div>
);
}

function App() {
return (
<div>
<header>
<nav>
<Link to="/dashboard">Dashboard</Link>
<Link to="/profile">Profile</Link>
<Link to="/settings">Settings</Link>
</nav>
</header>

<main>
<Suspense fallback={<LoadingSpinner />}>
<Routes>
<Route path="/dashboard" element={<Dashboard />} />
<Route path="/profile" element={<Profile />} />
<Route path="/settings" element={<Settings />} />
</Routes>
</Suspense>
</main>
</div>
);
}

Dynamic Imports

import { useState } from 'react';

function DynamicComponent() {
const [component, setComponent] = useState(null);
const [loading, setLoading] = useState(false);

const loadComponent = async componentName => {
setLoading(true);
try {
const module = await import(`./components/${componentName}`);
setComponent(() => module.default);
} catch (error) {
console.error('Failed to load component:', error);
} finally {
setLoading(false);
}
};

return (
<div>
<button onClick={() => loadComponent('Chart')}>Load Chart</button>
<button onClick={() => loadComponent('Table')}>Load Table</button>

{loading && <div>Loading component...</div>}
{component && <component />}
</div>
);
}

Bundle Optimization

Tree Shaking

// ❌ Bad: Imports entire library
import _ from 'lodash';
const result = _.debounce(func, 300);

// ✅ Good: Import only what you need
import debounce from 'lodash/debounce';
const result = debounce(func, 300);

// ✅ Good: Use ES6 imports for tree shaking
import { debounce } from 'lodash-es';

Webpack Bundle Analyzer

# Install bundle analyzer
npm install --save-dev webpack-bundle-analyzer

# Add to package.json scripts
"analyze": "npm run build && npx webpack-bundle-analyzer build/static/js/*.js"

# Run analysis
npm run analyze

Code Splitting Strategies

// Route-based splitting
const Home = lazy(() => import('./pages/Home'));
const About = lazy(() => import('./pages/About'));

// Feature-based splitting
const AdminPanel = lazy(() => import('./features/AdminPanel'));
const UserDashboard = lazy(() => import('./features/UserDashboard'));

// Component-based splitting
const HeavyChart = lazy(() => import('./components/HeavyChart'));
const DataTable = lazy(() => import('./components/DataTable'));

Profiling and Debugging

React DevTools Profiler

// Enable profiling in development
function App() {
return (
<React.Profiler id="App" onRender={onRenderCallback}>
<div className="App">
<Header />
<Main />
<Footer />
</div>
</React.Profiler>
);
}

function onRenderCallback(
id,
phase,
actualDuration,
baseDuration,
startTime,
commitTime,
interactions
) {
console.log('Profiler:', {
id,
phase,
actualDuration,
baseDuration,
startTime,
commitTime,
interactions,
});
}

Performance Monitoring

import { getCLS, getFID, getFCP, getLCP, getTTFB } from 'web-vitals';

function reportWebVitals(metric) {
console.log(metric);
// Send to analytics service
analytics.track('web-vital', metric);
}

// Measure Core Web Vitals
getCLS(reportWebVitals);
getFID(reportWebVitals);
getFCP(reportWebVitals);
getLCP(reportWebVitals);
getTTFB(reportWebVitals);

Custom Performance Hooks

import { useEffect, useRef } from 'react';

// Hook to measure render time
function useRenderTime(componentName) {
const renderStart = useRef(performance.now());

useEffect(() => {
const renderEnd = performance.now();
const renderTime = renderEnd - renderStart.current;

console.log(`${componentName} render time: ${renderTime.toFixed(2)}ms`);

// Reset for next render
renderStart.current = performance.now();
});
}

// Hook to detect slow renders
function useSlowRenderDetector(threshold = 16) {
const renderStart = useRef(performance.now());

useEffect(() => {
const renderEnd = performance.now();
const renderTime = renderEnd - renderStart.current;

if (renderTime > threshold) {
console.warn(`Slow render detected: ${renderTime.toFixed(2)}ms`);
}

renderStart.current = performance.now();
});
}

// Usage
function MyComponent() {
useRenderTime('MyComponent');
useSlowRenderDetector(20);

return <div>My Component</div>;
}

State Management Performance

Context Optimization

// ❌ Bad: Single large context
const AppContext = createContext();

function AppProvider({ children }) {
const [user, setUser] = useState(null);
const [theme, setTheme] = useState('light');
const [notifications, setNotifications] = useState([]);

// All components re-render when any value changes
return (
<AppContext.Provider
value={{
user,
setUser,
theme,
setTheme,
notifications,
setNotifications,
}}
>
{children}
</AppContext.Provider>
);
}

// ✅ Good: Split contexts by update frequency
const UserContext = createContext();
const ThemeContext = createContext();
const NotificationContext = createContext();

function UserProvider({ children }) {
const [user, setUser] = useState(null);
return (
<UserContext.Provider value={{ user, setUser }}>
{children}
</UserContext.Provider>
);
}

function ThemeProvider({ children }) {
const [theme, setTheme] = useState('light');
return (
<ThemeContext.Provider value={{ theme, setTheme }}>
{children}
</ThemeContext.Provider>
);
}

State Selector Optimization

// Using React-Redux with selectors
import { useSelector } from 'react-redux';
import { createSelector } from 'reselect';

// Memoized selector
const selectFilteredItems = createSelector(
state => state.items,
state => state.filter,
(items, filter) =>
items.filter(item => item.name.toLowerCase().includes(filter.toLowerCase()))
);

function ItemList() {
const filteredItems = useSelector(selectFilteredItems);
const filter = useSelector(state => state.filter);

return (
<div>
{filteredItems.map(item => (
<div key={item.id}>{item.name}</div>
))}
</div>
);
}

List Optimization

Virtualization

// Using react-window for large lists
import { FixedSizeList as List } from 'react-window';

function VirtualizedList({ items }) {
const Row = ({ index, style }) => (
<div style={style}>{items[index].name}</div>
);

return (
<List height={600} itemCount={items.length} itemSize={50} width="100%">
{Row}
</List>
);
}

Pagination

function PaginatedList({ items, pageSize = 10 }) {
const [currentPage, setCurrentPage] = useState(1);

const paginatedItems = useMemo(() => {
const startIndex = (currentPage - 1) * pageSize;
const endIndex = startIndex + pageSize;
return items.slice(startIndex, endIndex);
}, [items, currentPage, pageSize]);

const totalPages = Math.ceil(items.length / pageSize);

return (
<div>
<ul>
{paginatedItems.map(item => (
<li key={item.id}>{item.name}</li>
))}
</ul>

<div className="pagination">
<button
onClick={() => setCurrentPage(prev => Math.max(1, prev - 1))}
disabled={currentPage === 1}
>
Previous
</button>

<span>
Page {currentPage} of {totalPages}
</span>

<button
onClick={() => setCurrentPage(prev => Math.min(totalPages, prev + 1))}
disabled={currentPage === totalPages}
>
Next
</button>
</div>
</div>
);
}

Image and Asset Optimization

Lazy Loading Images

import { useState, useRef, useEffect } from 'react';

function LazyImage({ src, alt, placeholder, ...props }) {
const [imageSrc, setImageSrc] = useState(placeholder);
const [loading, setLoading] = useState(true);
const imageRef = useRef();

useEffect(() => {
const observer = new IntersectionObserver(
([entry]) => {
if (entry.isIntersecting) {
setImageSrc(src);
observer.disconnect();
}
},
{ threshold: 0.1 }
);

if (imageRef.current) {
observer.observe(imageRef.current);
}

return () => observer.disconnect();
}, [src]);

return (
<img
ref={imageRef}
src={imageSrc}
alt={alt}
loading="lazy"
onLoad={() => setLoading(false)}
style={{
filter: loading ? 'blur(5px)' : 'none',
transition: 'filter 0.3s',
}}
{...props}
/>
);
}

Image Optimization

// Using next/image for optimization
import Image from 'next/image';

function OptimizedImage({ src, alt, width, height }) {
return (
<Image
src={src}
alt={alt}
width={width}
height={height}
placeholder="blur"
blurDataURL="data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAYEBQYFBAYGBQYHBwYIChAKCgkJChQODwwQFxQYGBcUFhYaHSUfGhsjHBYWICwgIyYnKSopGR8tMC0oMCUoKSj/2wBDAQcHBwoIChMKChMoGhYaKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCj/wAARCAAIAAoDASIAAhEBAxEB/8QAFQABAQAAAAAAAAAAAAAAAAAAAAv/xAAhEAACAQMDBQAAAAAAAAAAAAABAgMABAUGIWEREiMxUf/EABUBAQEAAAAAAAAAAAAAAAAAAAMF/8QAGhEAAgIDAAAAAAAAAAAAAAAAAAECEgMRkf/aAAwDAQACEQMRAD8AltJagyeH0AthI5xdrLcNM91BF5pX2HaH9bcfaSXWGaRmknyJckliyjqTzSlT54b6bk+h0R+hBeCNvEMt7g=="
priority
/>
);
}

Common Performance Anti-patterns

Avoid These Patterns

// ❌ Bad: Inline object creation
function Component() {
return (
<div>
<ChildComponent style={{ color: 'red' }} />
<ChildComponent config={{ theme: 'dark' }} />
</div>
);
}

// ✅ Good: Extract objects
const redStyle = { color: 'red' };
const darkConfig = { theme: 'dark' };

function Component() {
return (
<div>
<ChildComponent style={redStyle} />
<ChildComponent config={darkConfig} />
</div>
);
}

// ❌ Bad: Inline function creation
function Component({ items }) {
return (
<div>
{items.map(item => (
<button key={item.id} onClick={() => handleClick(item.id)}>
{item.name}
</button>
))}
</div>
);
}

// ✅ Good: Use useCallback
function Component({ items }) {
const handleClick = useCallback(id => {
// Handle click
}, []);

return (
<div>
{items.map(item => (
<button key={item.id} onClick={() => handleClick(item.id)}>
{item.name}
</button>
))}
</div>
);
}

Unnecessary Re-renders

// ❌ Bad: Creating new objects in render
function Component({ user }) {
const userInfo = {
name: user.name,
email: user.email,
lastLogin: new Date(user.lastLogin).toLocaleDateString(),
};

return <UserCard user={userInfo} />;
}

// ✅ Good: Use useMemo
function Component({ user }) {
const userInfo = useMemo(
() => ({
name: user.name,
email: user.email,
lastLogin: new Date(user.lastLogin).toLocaleDateString(),
}),
[user.name, user.email, user.lastLogin]
);

return <UserCard user={userInfo} />;
}

Performance Monitoring

Real User Monitoring

// Monitor component performance
function usePerformanceMonitor(componentName) {
const startTime = useRef(performance.now());

useEffect(() => {
const endTime = performance.now();
const duration = endTime - startTime.current;

// Send to analytics
analytics.timing('component.render', duration, {
component: componentName,
});

startTime.current = performance.now();
});
}

// Track user interactions
function useInteractionTracking() {
useEffect(() => {
const handleClick = event => {
const start = performance.now();

// Measure time to next paint
requestAnimationFrame(() => {
const end = performance.now();
analytics.timing('interaction.response', end - start);
});
};

document.addEventListener('click', handleClick);
return () => document.removeEventListener('click', handleClick);
}, []);
}

Error Boundary Performance

class PerformanceErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}

static getDerivedStateFromError(error) {
return { hasError: true };
}

componentDidCatch(error, errorInfo) {
// Log performance-related errors
console.error('Performance Error:', error, errorInfo);

// Send to error tracking service
errorTracker.captureException(error, {
extra: errorInfo,
tags: { type: 'performance' },
});
}

render() {
if (this.state.hasError) {
return (
<div className="error-fallback">
<h2>Something went wrong</h2>
<p>The application encountered a performance issue.</p>
<button onClick={() => window.location.reload()}>Reload Page</button>
</div>
);
}

return this.props.children;
}
}

Quick Reference

Optimization Checklist

// ✅ Memoization
const MemoizedComponent = React.memo(MyComponent);
const memoizedValue = useMemo(() => expensiveCalculation(a, b), [a, b]);
const memoizedCallback = useCallback(() => doSomething(a, b), [a, b]);

// ✅ Code splitting
const LazyComponent = lazy(() => import('./LazyComponent'));

// ✅ Avoid inline objects/functions
const style = { color: 'red' };
const handleClick = useCallback(() => {}, []);

// ✅ Optimize lists
<VirtualizedList items={items} />

// ✅ Image optimization
<img loading="lazy" src={src} alt={alt} />

Performance Hooks

// Monitor render time
function useRenderTime(name) {
const start = useRef(performance.now());
useEffect(() => {
console.log(`${name}: ${performance.now() - start.current}ms`);
});
}

// Debounce updates
function useDebounce(value, delay) {
const [debouncedValue, setDebouncedValue] = useState(value);

useEffect(() => {
const handler = setTimeout(() => {
setDebouncedValue(value);
}, delay);

return () => clearTimeout(handler);
}, [value, delay]);

return debouncedValue;
}