Table of Contents
React Performance Optimization
Learn techniques to optimize React applications for better performance, including memoization, lazy loading, and efficient rendering strategies.
React.memo for Component Memoization
// Expensive component that should only re-render when props change
const ExpensiveComponent = React.memo(({ data, onUpdate }) => {
console.log('ExpensiveComponent rendered');
return (
{data.title}
{data.description}
);
});
// Custom comparison function
const ExpensiveComponentWithCustomComparison = React.memo(
({ data, onUpdate }) => {
return (
{data.title}
{data.description}
);
},
(prevProps, nextProps) => {
// Return true if props are equal (skip re-render)
return prevProps.data.id === nextProps.data.id &&
prevProps.data.title === nextProps.data.title;
}
);
useMemo for Expensive Calculations
function ProductList({ products, searchTerm, sortBy }) {
// Expensive filtering and sorting operation
const filteredAndSortedProducts = useMemo(() => {
console.log('Filtering and sorting products...');
let filtered = products.filter(product =>
product.name.toLowerCase().includes(searchTerm.toLowerCase())
);
return filtered.sort((a, b) => {
if (sortBy === 'price') {
return a.price - b.price;
} else if (sortBy === 'name') {
return a.name.localeCompare(b.name);
}
return 0;
});
}, [products, searchTerm, sortBy]);
return (
{filteredAndSortedProducts.map(product => (
))}
);
}
useCallback for Function Memoization
function TodoApp() {
const [todos, setTodos] = useState([]);
const [filter, setFilter] = useState('all');
// Without useCallback, this function is recreated on every render
const handleToggleTodo = useCallback((id) => {
setTodos(prevTodos =>
prevTodos.map(todo =>
todo.id === id ? { ...todo, completed: !todo.completed } : todo
)
);
}, []); // Empty dependency array since we use functional update
const handleDeleteTodo = useCallback((id) => {
setTodos(prevTodos => prevTodos.filter(todo => todo.id !== id));
}, []);
const filteredTodos = useMemo(() => {
switch (filter) {
case 'active':
return todos.filter(todo => !todo.completed);
case 'completed':
return todos.filter(todo => todo.completed);
default:
return todos;
}
}, [todos, filter]);
return (
);
}
Lazy Loading with React.lazy and Suspense
// Lazy load components
const Dashboard = React.lazy(() => import('./components/Dashboard'));
const Profile = React.lazy(() => import('./components/Profile'));
const Settings = React.lazy(() => import('./components/Settings'));
function App() {
return (
Loading... }>
} />
} />
} />
Virtualization for Large Lists
// Using react-window for virtualization
import { FixedSizeList as List } from 'react-window';
const Row = ({ index, style, data }) => (
{data[index].name}
{data[index].description}
);
function VirtualizedList({ items }) {
return (
{Row}
);
}
Optimizing Context Updates
// Split contexts to minimize re-renders
const UserContext = createContext();
const UserActionsContext = createContext();
function UserProvider({ children }) {
const [user, setUser] = useState(null);
// Memoize actions to prevent unnecessary re-renders
const actions = useMemo(() => ({
login: (userData) => setUser(userData),
logout: () => setUser(null),
updateProfile: (updates) => setUser(prev => ({ ...prev, ...updates }))
}), []);
return (
{children}
);
}
Debouncing User Input
// Custom hook for debouncing
function useDebounce(value, delay) {
const [debouncedValue, setDebouncedValue] = useState(value);
useEffect(() => {
const handler = setTimeout(() => {
setDebouncedValue(value);
}, delay);
return () => {
clearTimeout(handler);
};
}, [value, delay]);
return debouncedValue;
}
// Usage in search component
function SearchComponent() {
const [searchTerm, setSearchTerm] = useState('');
const debouncedSearchTerm = useDebounce(searchTerm, 300);
useEffect(() => {
if (debouncedSearchTerm) {
// Perform search
searchAPI(debouncedSearchTerm);
}
}, [debouncedSearchTerm]);
return (
setSearchTerm(e.target.value)}
placeholder="Search..."
/>
);
}
Bundle Splitting and Code Splitting
// Dynamic imports for code splitting
const loadDashboard = () => import('./Dashboard');
const loadAnalytics = () => import('./Analytics');
// Route-based code splitting
function App() {
return (
}>
}
/>
);
}
Performance Monitoring
// Using React DevTools Profiler API
import { Profiler } from 'react';
function onRenderCallback(id, phase, actualDuration, baseDuration, startTime, commitTime) {
console.log('Component:', id);
console.log('Phase:', phase);
console.log('Actual duration:', actualDuration);
}
function App() {
return (
);
}
Practical Exercise
Optimize a slow React application by:
- Identifying performance bottlenecks
- Implementing memoization where appropriate
- Adding lazy loading for routes
- Optimizing list rendering
Performance Best Practices
- Use React DevTools Profiler to identify bottlenecks
- Don't optimize prematurely - measure first
- Memoize expensive calculations and functions
- Implement lazy loading for large components
- Use virtualization for long lists
- Optimize images and assets
- Minimize bundle size with code splitting