React performance problems are predictable, and so are their solutions. 90% of React slowdowns come from four causes: unnecessary component re-renders, missing memoization on expensive computations, rendering 10,000 items in a list instead of virtualizing, and shipping 5MB JavaScript bundles. This guide covers every optimization technique with clear guidance on when to apply each.
⚡ TL;DR: Profile with React DevTools Profiler before optimizing. Use
React.memoto prevent child re-renders. UseuseMemofor expensive computations. UseuseCallbackfor stable function references. Virtualize lists over 100 items. Code-split withReact.lazy. Never define objects/arrays inline in JSX.
Profile first with React DevTools
// Enable React DevTools Profiler:
// 1. Open DevTools → Profiler tab
// 2. Click Record
// 3. Interact with slow UI
// 4. Click Stop
// 5. Look for: components with long render times, components
// that render when they shouldn't, renders triggered by parent
// The Flamegraph shows:
// - Gray bars: did not render this commit
// - Yellow/orange bars: slow renders
// - How many ms each component took
// - Why it re-rendered (props changed? state changed? parent re-rendered?)React.memo — prevent unnecessary re-renders
// Without memo: re-renders every time parent renders
function ExpensiveChild({ data }) {
// Heavy computation or large render tree
return {data.map(item => )};
}
// With memo: only re-renders when data prop changes
const ExpensiveChild = React.memo(function({ data }) {
return {data.map(item => )};
});
// Custom comparison for deep equality:
const ExpensiveChild = React.memo(function({ user }) {
return ;
}, (prevProps, nextProps) => {
// Return true = do NOT re-render
return prevProps.user.id === nextProps.user.id &&
prevProps.user.updatedAt === nextProps.user.updatedAt;
});
// When to use React.memo:
// - Component renders often
// - Component has expensive render
// - Props rarely change
// When NOT to use:
// - Component always re-renders with new props anyway
// - Comparison cost > render cost (simple components)useMemo and useCallback correctly
// useMemo: memoize expensive computed values
function ProductList({ products, searchQuery, sortBy }) {
// Without useMemo: recalculates on every render
// With useMemo: only recalculates when products, searchQuery, or sortBy changes
const filteredAndSorted = useMemo(() => {
return products
.filter(p => p.name.toLowerCase().includes(searchQuery.toLowerCase()))
.sort((a, b) => a[sortBy] > b[sortBy] ? 1 : -1);
}, [products, searchQuery, sortBy]);
return {filteredAndSorted.map(p => )}
;
}
// useCallback: stable function reference for child components
function Parent() {
const [count, setCount] = useState(0);
// Without useCallback: new function instance every render
// Child with memo would still re-render because handler is "new"
const handleClick = useCallback((id) => {
deleteItem(id);
}, []); // Empty deps: function never changes
return ;
}
// MISTAKE: useMemo for everything — adds overhead
// Only use useMemo when:
// - Computation takes >1ms (filter/sort large arrays, complex transforms)
// - Value passed as prop to memoized child componentThe inline object/array problem
// EVERY render creates a NEW object reference
// React.memo children will ALWAYS re-render
// Bad: new object on every render
setHovered(true)} // New function reference every render
/>
// Good: stable references
const CHART_STYLE = { color: "red", margin: 20 }; // Module level (never changes)
const DEFAULT_DATA = [1, 2, 3]; // Module level
function Parent() {
const handleHover = useCallback(() => setHovered(true), []);
return ;
} List virtualization — only render visible items
import { FixedSizeList } from 'react-window';
// BAD: renders 10,000 DOM nodes
function BadList({ items }) {
return (
{items.map(item => )}
);
// 10,000 DOM nodes: 3-5 second initial render, 500MB memory
}
// GOOD: renders only ~20 visible items (the viewport)
function VirtualizedList({ items }) {
const Row = ({ index, style }) => (
);
return (
{Row}
);
// Only ~8 DOM nodes visible at once, regardless of list size
// Rule: virtualize any list with >100 items
}Code splitting with React.lazy
import { lazy, Suspense } from 'react';
// Without code splitting: entire dashboard loaded on first visit
import HeavyDashboard from './Dashboard';
// With code splitting: Dashboard only loaded when navigated to
const HeavyDashboard = lazy(() => import('./Dashboard'));
const ReportsPage = lazy(() => import('./Reports'));
const AdminPanel = lazy(() => import('./Admin'));
function App() {
return (
}>
} />
} />
);
}
// Initial bundle: 200KB instead of 2MB
// Dashboard chunk: loaded on demand (~1.5MB separate)React performance checklist
- ✅ Profile with React DevTools Profiler before adding any optimization
- ✅
React.memofor components that render often with stable props - ✅
useMemofor expensive computations (filter/sort of large arrays) - ✅
useCallbackfor functions passed as props to memoized children - ✅ Never define objects, arrays, or functions inline in JSX props
- ✅ Virtualize any list with more than 100 items (react-window or react-virtual)
- ✅ Code-split every route with
React.lazy+Suspense - ✅ Use
keycorrectly — stable unique IDs, never array index - ❌ Don’t use
useMemofor cheap operations — the overhead can exceed the saving - ❌ Don’t memoize everything — profile first, optimize second
React performance is closely tied to JavaScript fundamentals — the WeakMap and WeakRef guide explains the memory model that makes React memo’s shallow comparison fast. For build-time optimization, V8 JIT optimization explains why monomorphic component props compile faster. External reference: React rendering docs.
Free Weekly Newsletter
🚀 Don’t Miss the Next Cheat Code
You just read something most developers never learn. Get more secrets like this delivered every week — JavaScript internals, Python optimizations, AWS architectures, system design, and AI workflows.
Related: JavaScript Async/Await: Every Pattern, Pitfall, and Performance Trick · Python List Comprehension, Generators, and Itertools: The Complete Performance Guide · PostgreSQL Performance Tuning: The Configuration Changes That Actually Matter
Related: Day 12: ReAct vs Plan-Then-Act vs Compiler Agents · React State Management: useState vs useReducer vs Context vs Zustand · React Hooks Explained: useState, useEffect, useCallback, useMemo, useRef In Depth
Watch: 100 Days of AI on YouTube
Short videos from CheatCoders, one AI engineering topic per day.
Most viewed
- Python Decorators Explained: From Simple Wrappers to Production Patterns
- Claude Code vs Cursor vs GitHub Copilot: Choose by Workflow, and Set Up One Repo That Works With All Three
- AI Agent Frameworks in 2025: LangGraph vs CrewAI vs AutoGen vs Raw API
- Python String Methods: Every str Method With Real Production Examples
- REST API Design Best Practices: The Patterns That Make APIs a Joy to Use
Newly added
- AWS Systems Manager Parameter Store: Hierarchical Runtime Config for Coding Agents
- Amazon Cognito Identity Pools: Federate Coding-Agent Tool Callers Without Long-Lived Keys
- Amazon Athena for Coding Agents: Query Tool Audit Logs and Eval Traces on S3 Without a Warehouse
- AWS Batch for Coding Agents: Parallel Overnight PR Reviews and Long-Running Jobs Without Lambda Timeouts
- Amazon Bedrock Prompt Management: Version and A/B Test Coding-Agent System Prompts
Deep-dive PDF
Get the expanded guide for this post — extra diagrams-style checklists, failure modes, and a production walkthrough. Free when you subscribe to CheatCoders.
Already subscribed? or open the subscribe page.
Discover more from CheatCoders
Subscribe to get the latest posts sent to your email.
