React Performance: 8 Fixes That Actually Matter (2026)
Stop over-optimizing. These 8 fixes solve 95% of React performance issues.
How to Measure First
// React DevTools Profiler — record, interact, analyze
// Look for: Long render times, unnecessary re-renders
// Chrome DevTools → Performance tab
// Record → interact → stop → check for long tasks
// Quick code check — count renders
const renderCount = useRef(0);
useEffect(() => {
renderCount.current++;
console.log(`Rendered ${renderCount.current} times`);
});
Fix 1: Memoize Expensive Computations
// ❌ Recalculates on EVERY render
function ProductList({ products, filter, sort }) {
const filtered = products
.filter(p => p.category === filter)
.sort((a, b) => a[sort] - b[sort]);
return filtered.map(p => <ProductCard key={p.id} {...p} />);
}
// ✅ Only recalculates when dependencies change
function ProductList({ products, filter, sort }) {
const filtered = useMemo(() =>
products
.filter(p => p.category === filter)
.sort((a, b) => a[sort] - b[sort]),
[products, filter, sort]
);
return filtered.map(p => <ProductCard key={p.id} {...p} />);
}
When to use: Array operations on 100+ items, complex calculations, expensive string parsing.
Fix 2: Prevent Unnecessary Callback Recreation
// ❌ New function reference every render → child re-renders
function Parent() {
const [count, setCount] = useState(0);
return (
<div>
<button onClick={() => setCount(c => c + 1)}>Count: {count}</button>
<ExpensiveChild onClick={() => console.log('clicked')} />
{/* ↑ New function every render! */}
</div>
);
}
// ✅ Stable function reference
function Parent() {
const [count, setCount] = useState(0);
const handleClick = useCallback(() => console.log('clicked'), []);
return (
<div>
<button onClick={() => setCount(c => c + 1)}>Count: {count}</button>
<ExpensiveChild onClick={handleClick} />
</div>
);
}
Fix 3: Lazy Load Routes and Heavy Components
import { lazy, Suspense } from 'react';
// ❌ All code loaded upfront
import Dashboard from './pages/Dashboard';
import Settings from './pages/Settings';
import Reports from './pages/Reports';
// ✅ Load on demand
const Dashboard = lazy(() => import('./pages/Dashboard'));
const Settings = lazy(() => import('./pages/Settings'));
const Reports = lazy(() => import('./pages/Reports'));
function App() {
return (
<Suspense fallback={<LoadingSpinner />}>
<Routes>
<Route path="/" element={<Dashboard />} />
<Route path="/settings" element={<Settings />} />
<Route path="/reports" element={<Reports />} />
</Routes>
</Suspense>
);
}
// Also works for heavy components within a page
const HeavyChart = lazy(() => import('./components/HeavyChart'));
Fix 4: Virtualize Long Lists
// ❌ Renders 10,000 items → slow scroll, high memory
function UserList({ users }) {
return (
<div>
{users.map(user => <UserRow key={user.id} {...user} />)}
</div>
);
}
// ✅ Only renders visible items (~20 at a time)
import { FixedSizeList as List } from 'react-window';
function UserList({ users }) {
const Row = ({ index, style }) => (
<div style={style}>
<UserRow {...users[index]} />
</div>
);
return (
<List
height={600} // Container height
itemCount={users.length}
itemSize={48} // Row height
width="100%"
>
{Row}
</List>
);
}
// Also: react-virtuoso (more features, easier API)
Fix 5: Optimize Context Usage
// ❌ Single context → entire tree re-renders on any change
const AppContext = createContext();
function AppProvider({ children }) {
const [user, setUser] = useState(null);
const [theme, setTheme] = useState('light');
const [notifications, setNotifications] = useState([]);
// ANY of these changing → ALL consumers re-render
return (
<AppContext.Provider value={{ user, setUser, theme, setTheme, notifications, setNotifications }}>
{children}
</AppContext.Provider>
);
}
// ✅ Split into separate contexts
const UserContext = createContext();
const ThemeContext = createContext();
const NotificationContext = createContext();
function AppProvider({ children }) {
return (
<UserProvider>
<ThemeProvider>
<NotificationProvider>
{children}
</NotificationProvider>
</ThemeProvider>
</UserProvider>
);
}
// Now: theme change only re-renders theme consumers
Fix 6: Use React.memo for Expensive Components
// ❌ Re-renders even when props haven't changed
function ExpensiveChart({ data, width, height }) {
// Heavy rendering...
}
// ✅ Only re-renders when props change
const ExpensiveChart = React.memo(function ExpensiveChart({ data, width, height }) {
// Heavy rendering...
});
// With custom comparison (if default shallow compare isn't enough)
const ExpensiveChart = React.memo(
function ExpensiveChart({ data, width, height }) {
// Heavy rendering...
},
(prevProps, nextProps) => {
return prevProps.data.id === nextProps.data.id; // Only re-render if data ID changes
}
);
Fix 7: Debounce Rapid Updates
import { useDebouncedCallback } from 'use-debounce';
// ❌ Search fires on every keystroke
function Search() {
const [query, setQuery] = useState('');
const [results, setResults] = useState([]);
useEffect(() => {
fetchResults(query).then(setResults); // Fires 60+ times per second while typing
}, [query]);
}
// ✅ Debounce — wait 300ms after last keystroke
function Search() {
const [query, setQuery] = useState('');
const [results, setResults] = useState([]);
const debouncedFetch = useDebouncedCallback(
(q) => fetchResults(q).then(setResults),
300
);
const handleChange = (e) => {
setQuery(e.target.value);
debouncedFetch(e.target.value);
};
}
// Also: debounce scroll handlers, resize handlers, drag handlers
Fix 8: Image Optimization
// ❌ Unoptimized images
<img src="/hero.jpg" alt="Hero" />
// ✅ Modern approach
<img
src="/hero-800.jpg"
srcSet="/hero-400.jpg 400w, /hero-800.jpg 800w, /hero-1200.jpg 1200w"
sizes="(max-width: 600px) 400px, (max-width: 1200px) 800px, 1200px"
alt="Hero"
loading="lazy"
decoding="async"
width={800}
height={400}
/>
// ✅ Even better: next/image (if using Next.js)
// ✅ Or: use a CDN with automatic optimization (Cloudinary, imgix)
What NOT to Optimize
Don't prematurely memoize everything. Profile first!
✅ DO optimize:
- Lists with 100+ items
- Components with heavy calculations
- Components that re-render unnecessarily (check with Profiler)
- Route-level code splitting (always)
❌ DON'T optimize (usually):
- Components that render once or twice
- Simple JSX with no calculations
- Event handlers that aren't passed as props
- Components with primitive props (already stable)
What React performance tips have saved your app?
Follow @armorbreak for more React content.
Top comments (0)