React - useMemo: Optimizing Calculations
When your component performs expensive calculations on every render, useMemo will memorize the result and only recalculate it if the dependencies change.
The Problem of Unnecessary Recalculations
Expensive Calculations on Every Render
function SlowComponent({ items, filter }) {
const [count, setCount] = useState(0)
// ❌ PROBLEM - Expensive calculation on EVERY render!
const expensiveValue = items
.filter(item => item.category === filter)
.reduce((sum, item) => sum + item.price, 0)
console.log('💰 Expensive calculation executed!') // ← Triggers even when count changes!
// ❌ PROBLEM - New object on every render!
const chartConfig = {
type: 'bar',
data: expensiveValue,
options: { responsive: true }
}
// ❌ PROBLEM - Array recalculated on every render!
const sortedItems = items
.filter(item => item.category === filter)
.sort((a, b) => b.price - a.price)
return (
<div>
<h3>Total: {expensiveValue}€</h3>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>+1</button>
<ExpensiveChart config={chartConfig} />
<ItemsList items={sortedItems} />
</div>
)
}
// These components re-render even if their props haven't really changed!
const ExpensiveChart = memo(function ExpensiveChart({ config }) {
console.log('📊 ExpensiveChart re-rendered') // ← Always called because config is always a new object!
return <div>Chart with config: {JSON.stringify(config)}</div>
})
const ItemsList = memo(function ItemsList({ items }) {
console.log('📝 ItemsList re-rendered') // ← Always called because items is always a new array!
return (
<ul>
{items.map(item => (
<li key={item.id}>{item.name} - {item.price}€</li>
))}
</ul>
)
})
useMemo to the Rescue
Syntax and Basic Usage
import { useMemo } from 'react'
function OptimizedComponent({ items, filter }) {
const [count, setCount] = useState(0)
// ✅ SOLUTION - Calculation only if items or filter change
const expensiveValue = useMemo(() => {
console.log('💰 Expensive calculation executed') // ← Only if necessary!
return items
.filter(item => item.category === filter)
.reduce((sum, item) => sum + item.price, 0)
}, [items, filter]) // ← Dependencies
// ✅ Complex object memorized
const chartConfig = useMemo(() => {
console.log('📊 Chart configuration generated')
return {
type: 'bar',
data: expensiveValue,
options: { responsive: true },
theme: 'modern'
}
}, [expensiveValue])
// ✅ Filtered and sorted data memorized
const sortedItems = useMemo(() => {
console.log('🔍 Filtering and sorting items')
return items
.filter(item => item.category === filter)
.sort((a, b) => b.price - a.price) // Sorting by descending price
}, [items, filter])
return (
<div>
<h3>Total: {expensiveValue}€</h3>
<p>Count: {count} (no longer triggers the calculation!)</p>
<button onClick={() => setCount(count + 1)}>+1</button>
<ExpensiveChart config={chartConfig} />
<ItemsList items={sortedItems} />
</div>
)
}
// Now these components only re-render if their props really change!
const ExpensiveChart = memo(function ExpensiveChart({ config }) {
console.log('📊 ExpensiveChart re-rendered') // ← Only if config really changes
return <div>Chart with config: {JSON.stringify(config)}</div>
})
const ItemsList = memo(function ItemsList({ items }) {
console.log('📝 ItemsList re-rendered') // ← Only if items really change
return (
<ul>
{items.map(item => (
<li key={item.id}>{item.name} - {item.price}€</li>
))}
</ul>
)
})