React
Intermediate
Performance Optimization in React
74 views
60 min read
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