React Performance Patterns — Optimization Techniques with Real World Examples
Learn how to make React apps faster using React.memo, useMemo, useCallback, code-splitting, and bundle optimization — with simple logic and real-world examples.
⚛️ React Performance Patterns
Make your React applications faster and smoother with smart optimization techniques.
🧠 Why React Performance Matters
React is fast, but poor component design and unnecessary re-renders can make even small apps slow.
Performance optimization means reducing unnecessary renders, optimizing computations, and loading code only when needed.
We’ll cover:
- React.memo and Component Memoization
- useMemo and useCallback
- Code splitting with React.lazy
- Bundle Analysis
- Virtual DOM performance
Each explained simply — with real-world logic and working code.
🧩 1. React.memo and Component Memoization
🔹 What It Does
React.memo prevents a component from re-rendering unless its props change.
By default, React re-renders child components whenever the parent updates — even if props stay the same.
Memoization helps skip those re-renders, saving time.
🔹 Simple Example
import React from "react";
const TaskItem = React.memo(function TaskItem({ title }) {
console.log("Rendering:", title);
return <li>{title}</li>;
});
export default function TaskList({ tasks }) {
return (
<ul>
{tasks.map((task) => (
<TaskItem key={task.id} title={task.title} />
))}
</ul>
);
}Here, TaskItem only re-renders when its title prop changes.
💡 Real-World Example:
In a task tracker app, only the edited task should update — not every item.
React.memo avoids re-rendering all tasks, improving speed as lists grow.
🔹 When to Use
✅ Use React.memo for:
- Lists of items
- Heavy UI components
- Components with expensive rendering logic
❌ Avoid it for tiny, fast-rendering components — memoization itself has a cost.
⚙️ 2. useMemo and useCallback Optimization
🔹 Why We Use Them
useMemo: Caches computed valuesuseCallback: Caches function references
They prevent React from recalculating or recreating the same things during every render.
🔹 Example: useMemo
import React, { useMemo, useState } from "react";
export default function PriceCalculator() {
const [items, setItems] = useState([10, 20, 30]);
const [discount, setDiscount] = useState(10);
const total = useMemo(() => {
console.log("Calculating total...");
return items.reduce((a, b) => a + b, 0) - discount;
}, [items, discount]);
return (
<>
<h3>Total: {total}</h3>
<button onClick={() => setDiscount(discount + 5)}>Increase Discount</button>
</>
);
}💡 Real-World Example:
An e-commerce checkout page can use useMemo to compute cart totals only when items or discounts change, not every render.
🔹 Example: useCallback
import React, { useCallback, useState } from "react";
function Button({ onClick, label }) {
console.log("Rendering:", label);
return <button onClick={onClick}>{label}</button>;
}
export default function Counter() {
const [count, setCount] = useState(0);
const increment = useCallback(() => setCount((c) => c + 1), []);
const decrement = useCallback(() => setCount((c) => c - 1), []);
return (
<>
<h3>Count: {count}</h3>
<Button onClick={increment} label="Increment" />
<Button onClick={decrement} label="Decrement" />
</>
);
}Here, useCallback ensures that increment and decrement are not recreated on every render —
so the Button component (wrapped in React.memo) doesn’t re-render unnecessarily.
💡 Real-World Example:
In data dashboards, you can use useCallback for filter buttons so that the chart doesn’t re-render when other UI parts update.
🔹 When to Use
| Hook | Use When | Example |
|---|---|---|
| useMemo | Computation is heavy | Price calculation, data sorting |
| useCallback | Function passed to children | Buttons, input handlers |
⚡ 3. Code Splitting with React.lazy
🔹 What It Does
Code splitting means dividing your app into smaller chunks so the browser loads only what’s needed — improving load time.
🔹 Example
import React, { Suspense, lazy } from "react";
const Reports = lazy(() => import("./Reports"));
const Dashboard = lazy(() => import("./Dashboard"));
export default function App() {
return (
<Suspense fallback={<p>Loading...</p>}>
<Dashboard />
<Reports />
</Suspense>
);
}💡 Real-World Example: When you open YouTube, it first loads the home feed quickly — while heavy pages like “Settings” or “Upload” load only when you click them. That’s code-splitting in action.
🔹 Tools to Help
React.lazyandSuspensefor component-level loadingdynamic import()for route-level code splitting- Build-time optimization with Webpack or Vite
📦 4. Bundle Analysis and Optimization
Large bundles slow down your app — especially on mobile. Bundle analysis helps identify unnecessary or duplicate code.
🔸 Step 1: Analyze Bundle Size
For Create React App:
npm run build
npx source-map-explorer "build/static/js/*.js"For Vite:
npm run build
npx vite-bundle-visualizerYou’ll see which libraries take the most space (like React, Lodash, or Moment.js).
🔸 Step 2: Optimize
| Problem | Solution |
|---|---|
| Big libraries | Replace with smaller alternatives (e.g., dayjs instead of moment.js) |
| Unused imports | Remove or lazy-load components |
| Repeated dependencies | Deduplicate via build tools |
| Large images | Compress and use WebP |
💡 Real-World Example: When Netflix optimized their React bundles, they cut initial load size by 30%, making the homepage load nearly twice as fast on slow networks.
🌳 5. Virtual DOM Performance Considerations
🔹 How React’s Virtual DOM Works
React uses a Virtual DOM — a lightweight copy of the real DOM. When state changes:
- React builds a new Virtual DOM tree.
- It compares (diffs) it with the old one.
- Only the changed parts update in the real DOM.
🔹 Why Performance Can Drop
- Too many re-renders (no memoization)
- Large component trees
- Inline functions recreated each render
- Unnecessary re-renders from parent updates
🔹 Optimization Techniques
| Problem | Fix |
|---|---|
| Frequent re-renders | Use React.memo, useMemo, useCallback |
| Heavy DOM updates | Split components into smaller parts |
| Expensive calculations | Use useMemo or move to background |
| Repeated props passing | Use Context or global store like Zustand or Redux |
💡 Real-World Example:
In Slack’s web app, typing messages caused slowdowns because all components re-rendered with every keystroke.
By using React.memo and splitting components, they improved typing performance by 40%.
🧾 Summary
| Concept | What It Does | Real-World Example |
|---|---|---|
| React.memo | Prevents unnecessary re-renders | Task Tracker list rendering |
| useMemo | Caches expensive calculations | E-commerce total price |
| useCallback | Caches functions | Dashboard filter buttons |
| React.lazy | Splits bundles for faster load | YouTube and Netflix modules |
| Bundle Analysis | Finds heavy files | Netflix reduced load size |
| Virtual DOM | Efficient rendering system | Slack typing performance boost |
💡 Final Thought
Performance in React is all about smart rendering and efficient loading. You don’t need to optimize everything — only what’s slow or frequently re-rendered.
A well-optimized React app:
- Loads faster
- Uses less memory
- Feels smoother
As apps scale, these patterns help your frontend stay as responsive as the world’s best — like Netflix, Slack, and YouTube.