The Need for Performance Optimization
React applications can experience performance issues when components re-render unnecessarily. By default, React follows a simple principle: when a component's state changes or it receives new props, it re-renders along with all of its child components, regardless of whether the children actually need to update.
This behavior is generally beneficial because it ensures UI consistency, but it can lead to performance problems in complex applications with:
- Deep component trees
- Computationally expensive rendering
- Frequent state updates
- Large lists of items
- Complex forms or interactive elements
In this diagram, a state change in the App component causes every component in the tree to re-render, even though the change might only affect one small part of the UI.
Real-World Analogy: Rebuilding a House
Imagine you own a house and want to repaint one room. The default React approach would be like demolishing and rebuilding the entire house just to change the color of that one room!
Just as it would be more efficient to only repaint the affected room, React optimization tools like React.memo allow us to only re-render the components that actually need to change.
Understanding Memoization
Before diving into React's memoization features, let's understand what memoization means in programming:
Memoization is an optimization technique that stores the results of expensive function calls and returns the cached result when the same inputs occur again, rather than recomputing the result.
A simple example of memoization in JavaScript:
// Without memoization
function fibonacci(n) {
if (n <= 1) return n;
return fibonacci(n - 1) + fibonacci(n - 2);
}
// With memoization
function memoizedFibonacci() {
const cache = {};
return function(n) {
if (n in cache) {
console.log('Returning from cache for', n);
return cache[n];
}
console.log('Calculating for', n);
if (n <= 1) {
cache[n] = n;
} else {
cache[n] = this(n - 1) + this(n - 2);
}
return cache[n];
};
}
const fib = memoizedFibonacci();
console.log(fib(5)); // Calculates for 0, 1, 2, 3, 4, 5
console.log(fib(5)); // Returns from cache for 5
In React, memoization is applied to components to avoid unnecessary re-renders when their inputs (props) haven't changed.
Why "Memo"?
The term "memo" in React.memo comes from "memoization," not "memorandum." It's about remembering previous outputs for given inputs, not about writing notes or reminders.
Think of it like a memorized formula: if you've already calculated 7 × 8 = 56, you don't need to recalculate it every time you encounter this multiplication. You just remember (or "memoize") the result.
Introduction to React.memo
React.memo is a higher-order component (HOC) that memoizes the result of a component render, preventing unnecessary re-renders if the props haven't changed.
When a component is wrapped in React.memo, React:
- Renders the component and memoizes (remembers) the result
- On subsequent renders, if the props are the same, React reuses the memoized result
- If the props change, React re-renders the component and stores the new result
Here's how to use React.memo:
import React from 'react';
// Regular functional component
function MyComponent(props) {
console.log('MyComponent rendered');
// Component implementation
return (
<div>
<h2>{props.title}</h2>
<p>{props.content}</p>
</div>
);
}
// Memoized version of the component
const MemoizedComponent = React.memo(MyComponent);
// Usage is the same as the original component
function App() {
return <MemoizedComponent title="Hello" content="World" />;
}
When MemoizedComponent receives the same title and content props on a re-render, it will skip rendering and reuse the previous result.
When to Use React.memo
React.memo is not a silver bullet for performance, and it's important to use it strategically. Here are the best scenarios for applying memoization:
Pure Functional Components
React.memo works best with pure functional components - components that render the same output given the same props, without side effects.
// Good candidate for React.memo
const ProductCard = React.memo(function ProductCard({ product, onAddToCart }) {
return (
<div className="product-card">
<img src={product.image} alt={product.name} />
<h3>{product.name}</h3>
<p>${product.price.toFixed(2)}</p>
<button onClick={() => onAddToCart(product.id)}>
Add to Cart
</button>
</div>
);
});
Components in Long Lists
Memoizing list item components is especially valuable because many items might re-render when only one changes.
// List component with memoized items
function ProductList({ products, onAddToCart }) {
return (
<div className="product-list">
{products.map(product => (
<ProductCard
key={product.id}
product={product}
onAddToCart={onAddToCart}
/>
))}
</div>
);
}
Components That Re-render Often
If a component is in a part of the tree that re-renders frequently, but its own props rarely change, memoization can help.
// This expensive component only depends on a few props
const ExpensiveChart = React.memo(function ExpensiveChart({ data, width, height }) {
console.log('Rendering expensive chart');
// Complex rendering logic here
return (
<div>
<canvas width={width} height={height} ref={/* chart rendering */} />
</div>
);
});
// Parent component that updates frequently
function Dashboard() {
const [count, setCount] = useState(0);
const [chartData] = useState(initialChartData); // This doesn't change
return (
<div>
<p>Counter: {count}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
{/* This chart won't re-render when count changes */}
<ExpensiveChart
data={chartData}
width={800}
height={400}
/>
</div>
);
}
Components with Expensive Calculations
If a component performs complex calculations during rendering, memoization can prevent redoing that work.
const DataAnalysis = React.memo(function DataAnalysis({ dataset }) {
// Expensive calculations
const summary = useMemo(() => {
console.log('Calculating dataset summary');
return {
average: dataset.reduce((sum, val) => sum + val, 0) / dataset.length,
max: Math.max(...dataset),
min: Math.min(...dataset)
};
}, [dataset]);
return (
<div>
<h2>Data Summary</h2>
<p>Average: {summary.average.toFixed(2)}</p>
<p>Maximum: {summary.max}</p>
<p>Minimum: {summary.min}</p>
</div>
);
});
Real-World Example: Product Filtering
Consider an e-commerce site with product filtering:
function ProductFilterPage() {
const [filters, setFilters] = useState({
category: 'all',
priceRange: [0, 1000],
inStock: false
});
const [sortOrder, setSortOrder] = useState('name');
const [products, setProducts] = useState([]);
// Fetch products (simulated)
useEffect(() => {
fetchProducts().then(setProducts);
}, []);
// Handle filter changes
const handleFilterChange = (newFilters) => {
setFilters({ ...filters, ...newFilters });
};
// Handle sort changes
const handleSortChange = (newSortOrder) => {
setSortOrder(newSortOrder);
};
return (
<div className="product-page">
{/* FilterPanel will re-render only when filters change */}
<FilterPanel
filters={filters}
onFilterChange={handleFilterChange}
/>
{/* SortControls will re-render only when sortOrder changes */}
<SortControls
sortOrder={sortOrder}
onSortChange={handleSortChange}
/>
{/* ProductGrid might re-render often as it uses both filters and sortOrder */}
<ProductGrid
products={products}
filters={filters}
sortOrder={sortOrder}
/>
</div>
);
}
// Memoized components
const FilterPanel = React.memo(function FilterPanel({ filters, onFilterChange }) {
console.log('FilterPanel rendered');
// Implementation...
});
const SortControls = React.memo(function SortControls({ sortOrder, onSortChange }) {
console.log('SortControls rendered');
// Implementation...
});
// ProductGrid is not memoized because it likely changes with both filters and sort
function ProductGrid({ products, filters, sortOrder }) {
console.log('ProductGrid rendered');
// Filter and sort the products
const filteredProducts = useMemo(() => {
return products
.filter(product => {
// Apply filters...
})
.sort((a, b) => {
// Apply sorting...
});
}, [products, filters, sortOrder]);
return (
<div className="product-grid">
{filteredProducts.map(product => (
<ProductCard key={product.id} product={product} />
))}
</div>
);
}
// Memoized product card for efficient rendering in lists
const ProductCard = React.memo(function ProductCard({ product }) {
console.log('ProductCard rendered:', product.id);
// Implementation...
});
In this example:
FilterPanelonly re-renders when filters changeSortControlsonly re-renders when the sort order changesProductGridre-renders when filters or sorting changes, but computes the filtered list efficientlyProductCardinstances only re-render when their specific product changes
This selective memoization strategy prevents unnecessary re-renders while keeping the code maintainable.
When NOT to Use React.memo
While React.memo can improve performance, it's not always beneficial:
1. Components That Almost Always Render with Different Props
If a component receives different props on every render, memoization adds overhead without benefits.
// Bad use of React.memo - props change every time
const BadMemoExample = React.memo(function TimeDisplay() {
const time = new Date().toLocaleTimeString(); // Different every render
return <div>Current time: {time}</div>;
});
// Better approach - just use a regular component
function TimeDisplay() {
const time = new Date().toLocaleTimeString();
return <div>Current time: {time}</div>;
}
2. Simple Components with Minimal Rendering Logic
For very simple components, the overhead of prop comparison might exceed the cost of just re-rendering.
// Probably not worth memoizing - too simple
const Greeting = React.memo(function Greeting({ name }) {
return <p>Hello, {name}!</p>;
});
// Just use a regular component
function Greeting({ name }) {
return <p>Hello, {name}!</p>;
}
3. Components That Rely on Context
If a component consumes context, it will re-render when the context changes regardless of props.
// Will still re-render when ThemeContext changes despite memoization
const ThemedButton = React.memo(function ThemedButton({ label }) {
const theme = useContext(ThemeContext);
return (
<button style={{ background: theme.background, color: theme.text }}>
{label}
</button>
);
});
4. Most Components in Your Application
Memoizing every component can lead to a more complex codebase without significant performance gains. Focus on the components that benefit most.
Premature Optimization Warning
Remember the famous quote by Donald Knuth:
"Premature optimization is the root of all evil." - Donald Knuth
Before applying memoization:
- Build the feature first with regular components
- Test if there are actual performance issues
- Use React's profiling tools to identify bottlenecks
- Apply memoization strategically where it provides measurable benefits
Adding memoization everywhere as a default strategy can make your code more complex and harder to maintain without necessarily improving performance.
Custom Comparison Functions
By default, React.memo performs a shallow equality check on the component's props. For complex props or special cases, you can provide a custom comparison function as the second argument.
function areEqual(prevProps, nextProps) {
// Return true if the props are equal (component shouldn't re-render)
// Return false if the props are different (component should re-render)
// Example: only re-render if the id or name changed
return (
prevProps.id === nextProps.id &&
prevProps.name === nextProps.name
// Ignoring other props
);
}
// Pass the custom comparison function as the second argument
const OptimizedComponent = React.memo(
function Component({ id, name, timestamp, data }) {
// Component implementation
},
areEqual
);
Comparing Complex Objects
When working with complex objects, defining a custom comparison function can be more efficient than having React compare every property:
// Custom comparison for a component with complex props
const UserProfile = React.memo(
function UserProfile({ user, stats, permissions }) {
// Component implementation
},
(prev, next) => {
// Only compare the properties we care about
return (
prev.user.id === next.user.id &&
prev.user.name === next.user.name &&
prev.stats.lastLogin === next.stats.lastLogin &&
prev.permissions.canEdit === next.permissions.canEdit
// Ignoring other nested properties that don't affect rendering
);
}
);
Performance Considerations for Comparison Functions
Custom comparison functions should be efficient, as they run on every render:
// Bad comparison function - too expensive
const ExpensiveComparison = React.memo(
function Component(props) {
// Component implementation
},
(prev, next) => {
// Don't do this! JSON.stringify is expensive
return JSON.stringify(prev) === JSON.stringify(next);
}
);
// Better approach - target specific properties
const BetterComparison = React.memo(
function Component({ items, config }) {
// Component implementation
},
(prev, next) => {
// Compare primitive properties directly
if (prev.config.sortOrder !== next.config.sortOrder) {
return false;
}
// For arrays, compare relevant aspects
if (prev.items.length !== next.items.length) {
return false;
}
// Additional, targeted comparisons
return prev.items.every((item, index) => item.id === next.items[index].id);
}
);
Real-World Example: Data Visualization Component
Data visualization components often have complex props but only need to re-render for specific changes:
const DataChart = React.memo(
function DataChart({
data,
dimensions,
options,
highlightedPoints,
onPointClick
}) {
// Complex chart rendering
return (
<div style={{ width: dimensions.width, height: dimensions.height }}>
{/* Chart implementation */}
</div>
);
},
(prev, next) => {
// Different dimensions should cause a re-render
if (prev.dimensions.width !== next.dimensions.width ||
prev.dimensions.height !== next.dimensions.height) {
return false;
}
// Different data length should cause a re-render
if (prev.data.length !== next.data.length) {
return false;
}
// Check if any data points changed their values
const dataChanged = prev.data.some((point, index) =>
point.x !== next.data[index].x ||
point.y !== next.data[index].y
);
if (dataChanged) {
return false;
}
// Check if relevant visual options changed
if (prev.options.color !== next.options.color ||
prev.options.showGrid !== next.options.showGrid ||
prev.options.lineStyle !== next.options.lineStyle) {
return false;
}
// Check if highlighted points changed
if (prev.highlightedPoints.length !== next.highlightedPoints.length ||
prev.highlightedPoints.some(id => !next.highlightedPoints.includes(id))) {
return false;
}
// Function references may change even if they're "the same"
// In a real app, you might use useCallback in the parent
// to stabilize these references
// If all checks pass, don't re-render
return true;
}
);
This example shows how a custom comparison function can optimize performance by checking only the properties that would visually affect the chart, ignoring changes that wouldn't impact the rendered output.
React.memo with Class Components
While React.memo is designed for functional components, it can also wrap class components. However, class components have their own optimization mechanism: React.PureComponent.
React.PureComponent vs. React.memo
// Using PureComponent for class components
class OptimizedClassComponent extends React.PureComponent {
render() {
console.log('OptimizedClassComponent rendered');
return (
<div>
<h2>{this.props.title}</h2>
<p>{this.props.content}</p>
</div>
);
}
}
// Using React.memo with a class component (less common)
class RegularClassComponent extends React.Component {
render() {
console.log('RegularClassComponent rendered');
return (
<div>
<h2>{this.props.title}</h2>
<p>{this.props.content}</p>
</div>
);
}
}
const MemoizedClassComponent = React.memo(RegularClassComponent);
React.PureComponent automatically implements a shouldComponentUpdate method with a shallow prop and state comparison, similar to how React.memo works.
Manual Optimization with shouldComponentUpdate
For more control, class components can implement shouldComponentUpdate directly:
class ManuallyOptimizedComponent extends React.Component {
shouldComponentUpdate(nextProps, nextState) {
// Custom logic to determine if update is needed
// Return true to update, false to skip update
return (
this.props.id !== nextProps.id ||
this.props.name !== nextProps.name ||
this.state.isActive !== nextState.isActive
);
}
render() {
console.log('ManuallyOptimizedComponent rendered');
return (
<div>
<h2>{this.props.name} (ID: {this.props.id})</h2>
<p>Status: {this.state.isActive ? 'Active' : 'Inactive'}</p>
</div>
);
}
}
Class Components vs. Functional Components
Modern React development increasingly favors functional components with hooks over class components. Here's why:
| Functional Components + Hooks | Class Components |
|---|---|
| Simpler and more concise | More verbose with boilerplate |
| Use React.memo for optimization | Use PureComponent or shouldComponentUpdate |
| Hooks provide state and lifecycle features | Built-in lifecycle methods |
| Better support for code reuse and composition | Uses HOCs and render props for composition |
| Focus of future React development | Legacy approach in modern React |
For new projects, using functional components with React.memo is the recommended approach for optimized components.
Debugging and Measuring Performance
Understanding when and why components are re-rendering is essential for effective optimization. React DevTools provides tools for analyzing component rendering.
Using the React DevTools Profiler
The Profiler helps you:
- Record rendering information for each component
- Identify components that render too often
- Measure rendering time for performance bottlenecks
- Visualize the "commit" timeline of React updates
To use the Profiler:
- Install React DevTools browser extension
- Open the DevTools and switch to the Profiler tab
- Click the record button and interact with your app
- Stop recording and analyze the results
Logging Component Renders
A simple way to track renders is to add console logs to your components:
// With regular component
function ExpensiveComponent({ data }) {
console.log('ExpensiveComponent rendered with:', data);
// Component implementation
}
// With memoized component
const MemoizedComponent = React.memo(function({ data }) {
console.log('MemoizedComponent rendered with:', data);
// Component implementation
});
React's useWhyDidYouUpdate Custom Hook
This custom hook can help identify which props are causing a component to re-render:
// Custom hook to track why a component updated
function useWhyDidYouUpdate(name, props) {
const previousProps = useRef();
useEffect(() => {
if (previousProps.current) {
// Get all keys from previous and current props
const allKeys = Object.keys({ ...previousProps.current, ...props });
// Find changed props
const changesObj = {};
allKeys.forEach(key => {
if (previousProps.current[key] !== props[key]) {
changesObj[key] = {
from: previousProps.current[key],
to: props[key]
};
}
});
// Log changes if any
if (Object.keys(changesObj).length) {
console.log('[why-did-you-update]', name, changesObj);
}
}
// Update previousProps with current props for next render
previousProps.current = props;
});
}
// Using the hook in a component
function OptimizableComponent(props) {
useWhyDidYouUpdate('OptimizableComponent', props);
return (
<div>
{/* Component content */}
</div>
);
}
Identifying Common Performance Issues
Here are typical problems that cause unnecessary re-renders:
-
Inline Function Props: Creating new function instances on every render
// Bad: New function created on every render <Button onClick={() => handleClick(id)} /> // Better: Stable function reference with useCallback const handleButtonClick = useCallback(() => { handleClick(id); }, [id, handleClick]); <Button onClick={handleButtonClick} /> -
Inline Object Props: Creating new object instances on every render
// Bad: New object created on every render <UserProfile user={{ name, email, avatar }} /> // Better: Create object before render or use useMemo const userObj = useMemo(() => ({ name, email, avatar }), [name, email, avatar]); <UserProfile user={userObj} /> -
Re-rendering Lists: When parent state changes, all list items re-render
// Before optimization: All items re-render when newItem changes function TodoList() { const [todos, setTodos] = useState([/* items */]); const [newItem, setNewItem] = useState(''); return ( <div> <input value={newItem} onChange={e => setNewItem(e.target.value)} /> {todos.map(todo => ( <TodoItem key={todo.id} todo={todo} /> ))} </div> ); } // After optimization: Memoize the TodoItem component const TodoItem = React.memo(function TodoItem({ todo }) { return <li>{todo.text}</li>; });
Common Pitfalls and Gotchas
Even with memoization, some common issues can prevent components from optimizing correctly:
1. New Object References in Props
Creating new objects or arrays in parent renders will cause memoized children to re-render:
// Problem: Child will always re-render despite React.memo
function Parent() {
const [count, setCount] = useState(0);
// New object created on every render
const data = { value: 42 };
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>
Increment
</button>
{/* MemoizedChild will re-render every time */}
<MemoizedChild data={data} />
</div>
);
}
const MemoizedChild = React.memo(function Child({ data }) {
console.log('Child rendered with data:', data);
return <p>Child value: {data.value}</p>;
});
// Solution: Move object creation outside render, use useMemo, or useState
function ImprovedParent() {
const [count, setCount] = useState(0);
// Stable object reference with useMemo
const data = useMemo(() => ({ value: 42 }), []);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>
Increment
</button>
{/* MemoizedChild won't re-render when count changes */}
<MemoizedChild data={data} />
</div>
);
}
2. New Function References
Similar to objects, new function instances cause memoization to fail:
// Problem: handleClick is a new function every render
function Parent() {
const [count, setCount] = useState(0);
// New function created on every render
const handleClick = () => {
console.log('Button clicked');
};
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>
Increment
</button>
{/* MemoizedButton will re-render every time */}
<MemoizedButton onClick={handleClick} label="Click Me" />
</div>
);
}
const MemoizedButton = React.memo(function Button({ onClick, label }) {
console.log('Button rendered with label:', label);
return <button onClick={onClick}>{label}</button>;
});
// Solution: Use useCallback to memoize the function
function ImprovedParent() {
const [count, setCount] = useState(0);
// Stable function reference with useCallback
const handleClick = useCallback(() => {
console.log('Button clicked');
}, []); // Empty dependency array means this never changes
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>
Increment
</button>
{/* MemoizedButton won't re-render when count changes */}
<MemoizedButton onClick={handleClick} label="Click Me" />
</div>
);
}
3. Prop Drilling Through Non-Memoized Components
Memoization is only effective if all components in the path are optimized:
// Problem: MiddleComponent breaks memoization chain
function Parent() {
const [count, setCount] = useState(0);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>
Increment
</button>
{/* MiddleComponent re-renders, causing MemoizedChild to re-render */}
<MiddleComponent count={count} extraProp="stable" />
</div>
);
}
// This component re-renders when count changes
function MiddleComponent({ count, extraProp }) {
return (
<div>
{/* Even though extraProp is stable, MemoizedChild will still re-render */}
<MemoizedChild data={extraProp} />
</div>
);
}
const MemoizedChild = React.memo(function Child({ data }) {
console.log('Child rendered with data:', data);
return <p>Child data: {data}</p>;
});
// Solution: Memoize the middle component or restructure component tree
const MemoizedMiddle = React.memo(function MiddleComponent({ extraProp }) {
return (
<div>
{/* Now MemoizedChild won't re-render when count changes */}
<MemoizedChild data={extraProp} />
</div>
);
});
function ImprovedParent() {
const [count, setCount] = useState(0);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>
Increment
</button>
{/* Using memoized middle component */}
<MemoizedMiddle extraProp="stable" />
</div>
);
}
Real-World Memoization Checklist
When implementing memoization, check for these common issues:
- ✅ All object props are memoized with useMemo or useState
- ✅ All function props are memoized with useCallback
- ✅ All components in the rendering chain are memoized when necessary
- ✅ Custom comparison functions are efficient and focus on relevant props
- ✅ Components that use context are aware that context changes will bypass memoization
- ✅ Performance is measured before and after to confirm improvements
Remember that over-optimization can be counterproductive. Apply memoization strategically where it provides measurable benefits.
Practice Activities
Activity 1: Identifying Memoization Opportunities
Analyze the following component structure and identify which components would benefit from memoization:
function Dashboard() {
const [user, setUser] = useState(null);
const [notifications, setNotifications] = useState([]);
const [activeTab, setActiveTab] = useState('overview');
useEffect(() => {
// Fetch user data
fetchUser().then(setUser);
// Fetch notifications every 30 seconds
fetchNotifications().then(setNotifications);
const interval = setInterval(() => {
fetchNotifications().then(setNotifications);
}, 30000);
return () => clearInterval(interval);
}, []);
return (
<div className="dashboard">
<Header
user={user}
notificationCount={notifications.length}
/>
<Sidebar
activeTab={activeTab}
onTabChange={setActiveTab}
/>
<main>
{activeTab === 'overview' && (
<Overview user={user} />
)}
{activeTab === 'stats' && (
<Statistics data={user?.stats} />
)}
{activeTab === 'notifications' && (
<NotificationsList
notifications={notifications}
onMarkAsRead={(id) => {
// Mark notification as read
}}
/>
)}
</main>
</div>
);
}
// For each component, determine if it should be memoized and explain why
// Implement React.memo where appropriate
Activity 2: Fixing Memoization Issues
The following code has issues preventing effective memoization. Identify and fix them:
function ProductPage() {
const [products, setProducts] = useState([]);
const [searchTerm, setSearchTerm] = useState('');
const [sortOrder, setSortOrder] = useState('name');
useEffect(() => {
fetchProducts().then(setProducts);
}, []);
// Filter and sort products
const filteredProducts = products
.filter(product =>
product.name.toLowerCase().includes(searchTerm.toLowerCase())
)
.sort((a, b) => {
if (sortOrder === 'name') {
return a.name.localeCompare(b.name);
} else {
return a.price - b.price;
}
});
return (
<div>
<div className="controls">
<input
type="text"
value={searchTerm}
onChange={e => setSearchTerm(e.target.value)}
placeholder="Search products..."
/>
<select
value={sortOrder}
onChange={e => setSortOrder(e.target.value)}
>
<option value="name">Sort by name</option>
<option value="price">Sort by price</option>
</select>
</div>
<div className="product-grid">
{filteredProducts.map(product => (
<ProductCard
key={product.id}
product={product}
onAddToCart={() => {
// Add to cart logic
console.log(`Added ${product.name} to cart`);
}}
/>
))}
</div>
</div>
);
}
// ProductCard is already memoized
const ProductCard = React.memo(function ProductCard({ product, onAddToCart }) {
console.log(`Rendering product: ${product.name}`);
return (
<div className="product-card">
<img src={product.image} alt={product.name} />
<h3>{product.name}</h3>
<p>${product.price.toFixed(2)}</p>
<button onClick={onAddToCart}>Add to Cart</button>
</div>
);
});
// Fix the issues preventing effective memoization
Activity 3: Custom Comparison Functions
Create a memoized data visualization component for a dashboard that only re-renders when relevant props change:
function DashboardChart({
title,
data,
width,
height,
showLegend,
colors,
onClick,
lastUpdated,
loading
}) {
console.log(`Rendering chart: ${title}`);
if (loading) {
return <div>Loading chart data...</div>;
}
return (
<div className="chart-container" style={{ width, height }}>
<h3>{title}</h3>
{showLegend && (
<div className="chart-legend">
{/* Legend items */}
</div>
)}
<div className="chart">
{/* Chart rendering based on data and colors */}
</div>
<div className="chart-footer">
Last updated: {new Date(lastUpdated).toLocaleString()}
</div>
</div>
);
}
// Create a memoized version with an appropriate custom comparison function
// Test it with different prop changes to verify it works correctly
Summary
React.memois a higher-order component that memoizes the result of a component's render operation- Memoization helps prevent unnecessary re-renders when props haven't changed
- Use memoization for pure functional components, components in lists, and components with expensive rendering
- Don't memoize simple components, components that always receive different props, or components that rely heavily on context
- Custom comparison functions allow fine-grained control over when components should re-render
- Class components can use
PureComponentorshouldComponentUpdatefor similar optimization - Common pitfalls include new object/function references and unoptimized components in the rendering chain
- Always measure performance before and after optimization to ensure benefits outweigh costs