React Performance Optimization Patterns
Use React.memo, useMemo, and useCallback correctly to prevent unnecessary re-renders.
Overview
React's rendering model can cause performance issues when components re-render unnecessarily. Understanding and applying the right optimization technique requires knowing why re-renders happen and which optimization addresses which problem. Premature optimization can add complexity without benefit. React.memo is a higher-order component that memoizes a component's output based on its props. If props don't change, the component won't re-render. However, React performs shallow comparison of props, so objects, arrays, and functions created in parent components will always be "different" between renders. memo should wrap components that are expensive to render and receive primitive or stable reference props. useMemo returns a memoized value computed from its dependencies. It's appropriate for expensive calculations that don't need to run on every render. The dependency array is critical—incorrect dependencies cause stale calculations or wasted recomputation. Setting the wrong dependencies is a common source of bugs. useCallback returns a memoized function, which is essentially useMemo for functions. It's necessary when passing callbacks to child components wrapped in React.memo—the function reference must remain stable across renders. However, overusing useCallback can hurt performance by adding memory overhead and the comparison cost itself. Profiling with React DevTools Profiler identifies actual performance bottlenecks before optimizing. The commitment phase shows what actually rendered and how long it took. Optimizations should target components with high render counts and visible latency rather than applying blanket memoization everywhere.
Code Example
import React, { useState, useMemo, useCallback, memo, useEffect } from 'react';
// Expensive calculation helper
function calculateExpensiveValue(items: Item[], filter: string): number {
console.log('Calculating...');
return items
.filter(item => item.name.includes(filter))
.reduce((sum, item) => sum + item.value, 0);
}
// Memoized child component
const ExpensiveList = memo(function ExpensiveList({
items,
onItemClick,
filter,
}: {
items: Item[];
onItemClick: (id: string) => void;
filter: string;
}) {
const filteredItems = useMemo(() => {
return items.filter(item => item.name.toLowerCase().includes(filter.toLowerCase()));
}, [items, filter]);
return (
<ul>
{filteredItems.map(item => (
<li key={item.id} onClick={() => onItemClick(item.id)}>
{item.name} - {item.value}
</li>
))}
</ul>
);
});
function SearchableList({ items }: { items: Item[] }) {
const [filter, setFilter] = useState('');
const [selectedId, setSelectedId] = useState<string | null>(null);
// Memoize callback to prevent re-render of memoized child
const handleItemClick = useCallback((id: string) => {
setSelectedId(prev => prev === id ? null : id);
}, []);
// Memoize expensive calculation
const totalValue = useMemo(
() => calculateExpensiveValue(items, filter),
[items, filter]
);
// Memoize filter options
const filterOptions = useMemo(() => {
return [...new Set(items.map(item => item.category))];
}, [items]);
return (
<div>
<input
value={filter}
onChange={e => setFilter(e.target.value)}
placeholder="Filter items..."
/>
<div>Total value: {totalValue}</div>
<ExpensiveList
items={items}
onItemClick={handleItemClick}
filter={filter}
/>
<div>Selected: {selectedId || 'None'}</div>
</div>
);
}
// Custom hook for debounced value
function useDebounce<T>(value: T, delay: number): T {
const [debouncedValue, setDebouncedValue] = useState(value);
useEffect(() => {
const handler = setTimeout(() => {
setDebouncedValue(value);
}, delay);
return () => clearTimeout(handler);
}, [value, delay]);
return debouncedValue;
}More React Rules
React Component Testing with Testing Library
Test behavior over implementation. Use Testing Library queries to interact with components as users would.
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { LoginForm }...React Server Components Data Fetching Patterns
Master async/await patterns in Server Components for efficient data loading and caching.
import { db } from '@/lib/db';
import { cache } from 'next/cache';
import { Suspense } from 'react';
import { Skeleton } from '@/components/ui/skeleto...React Form Handling with React Hook Form
Build performant forms with React Hook Form using uncontrolled components and validation.
import { useForm, Controller } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';
import DatePick...