Memoization and Optimization — Complete Guide
In this tutorial, you will learn about Memoization and Optimization. We cover key concepts, practical examples, and best practices to help you master this topic.
Memoization in Virtual Dom caches expensive computations and component outputs, preventing unnecessary re-renders and improving application performance significantly.
What You'll Learn
- What memoization is and how it applies to Virtual DOM
- How React.memo, useMemo, and useCallback work
- How to identify and fix unnecessary re-renders
- Advanced optimization patterns for large applications
Why It Matters
Without memoization, every parent re-render causes all children to re-render. For deep component trees with expensive computations, this creates unnecessary work that slows down the application.
flowchart LR
A[Parent re-renders] --> B{Child memoized?}
B -->|Yes| C{Props changed?}
B -->|No| D[Child re-renders]
C -->|Yes| D
C -->|No| E[Child skips re-render]
D --> F[Virtual DOM diffed]
E --> G[Subtree bailed out]
What Memoization Does
Memoization caches results based on input dependencies.
// Without memoization: every render recomputes
function ExpensiveComponent({ items, filter }) {
// This runs on EVERY render of the parent
const filtered = items.filter(item => {
// Expensive operation
return item.name.includes(filter) &&
complexCheck(item.data);
});
return filtered.map(item => <li key={item.id}>{item.name}</li>);
}
// With memoization: only recomputes when dependencies change
function ExpensiveComponent({ items, filter }) {
const filtered = useMemo(() => {
// Only runs when items or filter change
return items.filter(item => {
return item.name.includes(filter) &&
complexCheck(item.data);
});
}, [items, filter]); // Dependencies
return filtered.map(item => <li key={item.id}>{item.name}</li>);
}
// Without useMemo: O(n) filtering every render
// With useMemo: O(n) filtering only when items/filter change
React.memo for Components
React.memo prevents component re-rendering when props haven't changed.
// Unoptimized: re-renders on every parent render
function Child({ name, onClick }) {
console.log('Child rendered:', name);
return <button onClick={onClick}>{name}</button>;
}
// Optimized: only re-renders when props change (shallow comparison)
const MemoizedChild = React.memo(function Child({ name, onClick }) {
console.log('Child rendered:', name);
return <button onClick={onClick}>{name}</button>;
});
// Parent component
function Parent() {
const [count, setCount] = useState(0);
const [items, setItems] = useState(['A', 'B', 'C']);
// Problem: new function reference every render
// Even with React.memo, the child re-renders because onClick changes
const handleClick = () => {
console.log('Clicked');
};
// Solution: stable reference with useCallback
const stableHandleClick = useCallback(() => {
console.log('Clicked');
}, []); // No dependencies — stable for entire lifecycle
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(c => c + 1)}>Increment</button>
<MemoizedChild name="Stable" onClick={stableHandleClick} />
{/* Child does NOT re-render when count changes */}
</div>
);
}
Identifying Unnecessary Re-Renders
Use tools and techniques to find optimization opportunities.
// 1. React DevTools: Highlight updates
// Enable "Highlight updates when components render" in React DevTools
// Green flashes indicate re-renders. Look for unnecessary flashes.
// 2. Custom logging hook
function useWhyDidYouUpdate(name, props) {
const previousProps = useRef();
useEffect(() => {
if (previousProps.current) {
const allKeys = Object.keys({ ...previousProps.current, ...props });
const changedProps = {};
allKeys.forEach(key => {
if (previousProps.current[key] !== props[key]) {
changedProps[key] = {
from: previousProps.current[key],
to: props[key]
};
}
});
if (Object.keys(changedProps).length > 0) {
console.log('Component', name, 're-rendered because:', changedProps);
}
}
previousProps.current = props;
});
}
// Usage:
function MyComponent(props) {
useWhyDidYouUpdate('MyComponent', props);
return <div>{props.value}</div>;
}
// 3. Performance profiling
// Use React DevTools Profiler to record a session
// Look for components that rendered without prop changes
// These are candidates for memoization
Advanced Memoization Patterns
Beyond basic useMemo and useCallback.
// Pattern 1: Context splitting
// Instead of one large context, split by update frequency
const ThemeContext = createContext('light');
const UserContext = createContext(null);
const NotificationContext = createContext([]);
// Components that only need theme don't re-render
// when notifications change.
// Each context consumer only re-renders when its context changes.
// Pattern 2: Component lifting
// Move the stateful part above the memoized part
function Parent() {
return (
<ExpensiveList>
<Counter /> {/* Counter re-renders independently */}
</ExpensiveList>
);
}
const ExpensiveList = React.memo(function({ children }) {
return <div>{children}</div>;
});
// ExpensiveList doesn't re-render when Counter changes.
// Pattern 3: Selector functions
// Extract specific values from large objects
function DataView({ data }) {
// Only re-render when specific fields change
const title = useMemo(() => data.title, [data.title]);
const items = useMemo(() => data.items, [data.items]);
return (
<div>
<h1>{title}</h1>
<List items={items} />
</div>
);
}
// Pattern 4: Stable references for callbacks
const handlers = useRef({
onSave: (data) => saveData(data),
onDelete: (id) => deleteItem(id)
});
// Never causes child re-renders due to callback changes
Profiling-Guided Optimization
Always measure before optimizing.
// 1. Add performance marks to identify slow components
function ProfiledComponent() {
const startTime = useRef(performance.now());
const renderCount = useRef(0);
renderCount.current += 1;
useEffect(() => {
const elapsed = performance.now() - startTime.current;
if (elapsed > 5) {
console.warn('Slow render:', elapsed.toFixed(2) + 'ms', 'renders:', renderCount.current);
}
startTime.current = performance.now();
});
// Component content
return <ExpensiveContent />;
}
// 2. Use requestAnimationFrame for animation performance
function AnimationFrameLogger() {
const frameCount = useRef(0);
useEffect(() => {
let isActive = true;
function logFPS() {
if (!isActive) return;
frameCount.current += 1;
requestAnimationFrame(logFPS);
}
requestAnimationFrame(logFPS);
const interval = setInterval(() => {
console.log('FPS:', frameCount.current);
frameCount.current = 0;
}, 1000);
return () => {
isActive = false;
clearInterval(interval);
};
}, []);
}
// 3. Only memoize when there's a measurable benefit
// Premature memoization adds code complexity without benefit
Common Mistakes
- Memoizing everything without measuring first (premature optimization).
- Using useMemo for primitive values (numbers, strings) that are cheap to compute.
- Forgetting dependencies in useMemo/useCallback, causing stale values.
- Using inline objects/arrays in JSX that break React.memo's shallow comparison.
- Overusing useCallback when the child component isn't memoized with React.memo.
Practice Questions
- What does React.memo do? It prevents a component from re-rendering if its props haven't changed (shallow comparison).
- What is the difference between useMemo and useCallback? useMemo caches a computed value. useCallback caches a function reference.
- When should you use useMemo? When a computation is expensive and its dependencies change infrequently.
- What breaks React.memo? Inline objects, arrays, and functions that create new references every render.
Challenge
Build a component tree with 50 nested components where each component renders a random number. Use React DevTools Profiler to identify unnecessary re-renders. Apply React.memo, useMemo, and useCallback to eliminate all unnecessary re-renders. Show the before and after comparison.
FAQ
Mini Project
Build a React optimization dashboard. Create a complex component with multiple levels of nesting, expensive computations, and frequently updating state. Show a live display of which components are re-rendering and why. Apply optimizations step by step and show the reduction in render count and render time.
What's Next
Lesson 14: Virtual DOM and SSR
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro