The React Performance Guide: Making Your App Blazing Fast
โก Quick Answer
React performance optimization guide: memo, useMemo, useCallback, lazy loading, virtualization, bundle splitting, and profiling โ with real examples.
Get more content like this on Telegram!
Daily AI tips, notes & resources โ free
Advertisement
The React Performance Guide: Making Your App Blazing Fast
I optimized a product page that was taking 3.2 seconds to become interactive. The culprit: a single component was re-rendering 47 times on initial load because of a chain of poorly-placed state and missing memoization.
After profiling, the fixes took 20 minutes. Time to interactive dropped to 0.9 seconds.
Performance optimization in React is not about magic โ it's about understanding why React re-renders, measuring where time is actually spent, and applying targeted fixes. Premature optimization is the root of a lot of wasted effort in React codebases.
This guide gives you the mental model, the measurement tools, and the optimization techniques. In that order.
First: Understand the React Render Cycle
React re-renders a component when:
- Its state changes
- Its parent re-renders (default behavior)
- Its context value changes
function Parent() {
const [count, setCount] = useState(0);
return (
<div>
<button onClick={() => setCount(c => c + 1)}>Count: {count}</button>
<Child /> {/* Re-renders EVERY time Parent re-renders, even though Child has no props */}
</div>
);
}Re-rendering is not inherently bad โ React is designed to handle many re-renders efficiently. The problem is when:
- A component is expensive to render (complex calculations, large DOM trees)
- A component re-renders when its props haven't changed
Step 1: Measure Before You Optimize
React DevTools Profiler shows you what actually re-renders and how long it takes:
- Install React DevTools browser extension
- Open DevTools โ Profiler tab
- Click "Record", interact with your app, click "Stop"
- Inspect the flame graph โ slow renders appear in red/orange
Don't skip this step. I've seen developers add useMemo everywhere to "optimize" when profiling shows the actual bottleneck is a network request or a third-party library.
Technique 1: React.memo โ Skip Re-Renders
React.memo wraps a component so it only re-renders when its props actually change:
// Without memo: re-renders whenever parent re-renders
function TaskItem({ task, onToggle }: { task: Task; onToggle: (id: number) => void }) {
console.log('TaskItem rendered:', task.id);
return (
<div onClick={() => onToggle(task.id)}>
{task.text} โ {task.done ? 'โ' : 'โ'}
</div>
);
}
// With memo: only re-renders when task or onToggle changes
const TaskItem = React.memo(function TaskItem({ task, onToggle }) {
console.log('TaskItem rendered:', task.id);
return (
<div onClick={() => onToggle(task.id)}>
{task.text} โ {task.done ? 'โ' : 'โ'}
</div>
);
});The catch: React.memo does a shallow comparison of props. If you pass a new object or function reference on every render, memo doesn't help.
function TaskList() {
const [tasks, setTasks] = useState([...]);
// NEW function created on every render โ memo is useless
const handleToggle = (id: number) => {
setTasks(tasks.map(t => t.id === id ? { ...t, done: !t.done } : t));
};
return tasks.map(task => (
<TaskItem key={task.id} task={task} onToggle={handleToggle} />
// TaskItem re-renders anyway because handleToggle is new each render
));
}Fix with useCallback:
Technique 2: useCallback โ Stable Function References
function TaskList() {
const [tasks, setTasks] = useState([...]);
// Stable reference โ same function object across renders
const handleToggle = useCallback((id: number) => {
setTasks(prev => prev.map(t => t.id === id ? { ...t, done: !t.done } : t));
}, []); // Empty array: function never changes
return tasks.map(task => (
<TaskItem key={task.id} task={task} onToggle={handleToggle} />
// Now React.memo works correctly
));
}Note: handleToggle uses prev (functional state update) instead of tasks directly โ this avoids capturing stale state.
Technique 3: useMemo โ Cache Expensive Calculations
function ProductList({ products, searchQuery, sortBy }: Props) {
// This filters and sorts on EVERY render
const displayProducts = products
.filter(p => p.name.toLowerCase().includes(searchQuery.toLowerCase()))
.sort((a, b) => {
if (sortBy === 'price') return a.price - b.price;
return a.name.localeCompare(b.name);
});
// BETTER: only recalculate when products, searchQuery, or sortBy changes
const displayProducts = useMemo(() =>
products
.filter(p => p.name.toLowerCase().includes(searchQuery.toLowerCase()))
.sort((a, b) => {
if (sortBy === 'price') return a.price - b.price;
return a.name.localeCompare(b.name);
}),
[products, searchQuery, sortBy]
);
return displayProducts.map(product => <ProductCard key={product.id} product={product} />);
}Only use useMemo when the calculation is genuinely expensive (sorting/filtering thousands of items, complex computations). For simple array operations on small arrays, the useMemo overhead outweighs the benefit.
Technique 4: Code Splitting with React.lazy
import React, { Suspense, lazy } from 'react';
// Load AdminDashboard only when it's first rendered
const AdminDashboard = lazy(() => import('./AdminDashboard'));
const HeavyChart = lazy(() => import('./HeavyChart'));
const SettingsPanel = lazy(() => import('./SettingsPanel'));
function App() {
const [view, setView] = useState<'home' | 'admin' | 'settings'>('home');
return (
<div>
<nav>{/* navigation */}</nav>
<Suspense fallback={<div className="loading">Loading...</div>}>
{view === 'admin' && <AdminDashboard />}
{view === 'settings' && <SettingsPanel />}
{view === 'home' && <HeavyChart />}
</Suspense>
</div>
);
}The admin dashboard, chart library, and settings panel are only downloaded when the user navigates to them. Initial bundle size โ and Time to Interactive โ improves significantly.
Technique 5: Virtualize Long Lists
Rendering 1,000 DOM nodes is slow. Render only what's visible:
npm install react-windowimport { FixedSizeList } from 'react-window';
function VirtualizedList({ items }: { items: Item[] }) {
const Row = ({ index, style }: { index: number; style: React.CSSProperties }) => (
<div style={style} className="list-item">
{items[index].name}
</div>
);
return (
<FixedSizeList
height={600} // Visible height of the list
itemCount={items.length}
itemSize={50} // Height of each item in px
width="100%"
>
{Row}
</FixedSizeList>
);
}For variable-height items, use VariableSizeList. For tables, consider react-virtuoso which handles both.
Technique 6: State Colocation
Move state down to where it's used. Unnecessary high-level state causes unnecessary re-renders:
// Bad: count state in parent causes entire parent to re-render
function ExpensiveParent() {
const [count, setCount] = useState(0);
return (
<div>
<button onClick={() => setCount(c => c + 1)}>{count}</button>
<VeryExpensiveComponent /> {/* Re-renders on every count change */}
</div>
);
}
// Good: extract counter into its own component
function Counter() {
const [count, setCount] = useState(0);
return <button onClick={() => setCount(c => c + 1)}>{count}</button>;
}
function ExpensiveParent() {
return (
<div>
<Counter /> {/* Counter state stays inside Counter */}
<VeryExpensiveComponent /> {/* Never re-renders due to count */}
</div>
);
}This pattern โ colocating state โ is often more impactful than adding memo or useMemo.
Performance Checklist
Before reaching for memo, useMemo, or useCallback, run through this order:
- Profile first โ open React DevTools Profiler and confirm the slow component before touching code.
- Colocate state โ move state down to the smallest component that needs it; this alone fixes most unnecessary re-renders.
- Memoize only measured hotspots โ apply
React.memo/useMemo/useCallbackto components the profiler flagged, not everywhere. - Code-split heavy routes โ lazy-load admin panels, charts, and modals that aren't needed on first paint.
- Virtualize long lists โ anything rendering more than a couple hundred DOM nodes at once.
Optimization that isn't backed by a profiler trace is guesswork โ and guesswork tends to add complexity without moving the number that matters.
For the React fundamentals that these optimizations build on, see our React hooks tutorial. State management choices significantly impact performance โ our React state management 2025 guide covers how Zustand and Jotai minimize unnecessary re-renders. And for the full React app architecture that influences performance, our React vs Next.js vs Remix guide covers rendering strategy decisions.
Further Reading
- How to Deploy a React App to Vercel in 10 Minutes
- 10 JavaScript Tricks That Made Me a Better Developer Overnight
- The JavaScript Roadmap for 2025: What to Learn and in What Order
- Tailwind CSS vs Bootstrap in 2025: The Final Verdict
- How to Pass a JavaScript Interview at Google, Meta, or Amazon
- HTML5 and CSS3 for Beginners: Building Your First Real Website
- Django vs Flask in 2025: Which Framework Should You Learn?
- How Python Is Used in Finance: Real Wall Street Use Cases
Advertisement
๐ฌ DiscussionPowered by GitHub Discussions
Frequently Asked Questions
Problem Solver and Cloud Expert
Solves complex infrastructure challenges and architects reliable, scalable cloud deployments for every project. Shamshur Rahman keeps AiTechWorldsโ hosting and cloud infrastructure fast, resilient, and cost-efficient.
Not sure yet? Ask AI about this article
Get an instant, unbiased AI summary of โThe React Performance Guide: Making Your App Blazing Fastโ.
Advertisement
Related Articles
How to Deploy a React App to Vercel in 10 Minutes
Deploy a React app to Vercel in 10 minutes: from npm create vite to live URL, custom domain setup, environment variables, and preview deployments.
GraphQL vs REST: Which API Style Should You Learn in 2026?
GraphQL vs REST API compared honestly for 2026: when each makes sense, real code examples, and which API style to learn first as a developer.
JavaScript Promises and Async/Await: Finally Understand Them
JavaScript async await and Promises explained clearly: the event loop, Promise chains, async/await patterns, error handling, and common mistakes to avoid.
How to Pass a JavaScript Interview at Google, Meta, or Amazon
How to pass a JavaScript interview at top tech companies: closures, event loop, promises, DOM questions, system design, and real interview questions answered.