useMemo
The useMemo hook is a performance optimization tool in React that lets you cache the result of a calculation between re-renders. It is used to prevent expensive operations from running on every render or to maintain referential equality for objects and arrays.
Basic Usage
useMemo takes two arguments:
- A calculation function that takes no arguments and returns a value.
- A dependency array containing all the reactive values referenced inside the calculation.
import { useState, useMemo } from 'react';
function filterTodos(todos, filter) {
// Expensive filtering logic...
console.log('Filtering todos...');
return todos.filter(todo => todo.text.includes(filter));
}
export default function TodoList({ todos, filter }) {
const [text, setText] = useState('');
// The filteredTodos will only be recalculated when 'todos' or 'filter' changes.
// Typing in the input (changing 'text') will not trigger the expensive calculation.
const filteredTodos = useMemo(
() => filterTodos(todos, filter),
[todos, filter]
);
return (
<div>
<input value={text} onChange={(e) => setText(e.target.value)} />
<ul>
{filteredTodos.map(todo => (
<li key={todo.id}>{todo.text}</li>
))}
</ul>
</div>
);
}
When to use useMemo
1. Skipping expensive calculations
If a function takes a noticeable amount of time to execute (e.g., sorting a large array, complex math operations, or heavy data transformations), wrap it in useMemo.
2. Maintaining Referential Equality
In JavaScript, {} is not equal to {}. A new object or array is created on every render, which can cause child components wrapped in React.memo to unnecessarily re-render, or trigger useEffect hooks that depend on that object.
import { useMemo, useEffect } from 'react';
export default function Dashboard({ userId }) {
// Without useMemo, 'options' would be a new object on every render,
// causing the useEffect to fire endlessly or unnecessarily.
const options = useMemo(() => {
return {
serverUrl: 'https://api.example.com',
userId: userId
};
}, [userId]);
useEffect(() => {
const connection = createConnection(options);
connection.connect();
return () => connection.disconnect();
}, [options]);
}
Over-memoization Pitfalls
A common mistake is wrapping every value in useMemo. This is an anti-pattern.
- Memory Overhead:
useMemotakes up memory to store the previous inputs and the result. - Execution Overhead: React has to check the dependency array on every render, which is sometimes slower than just re-running a cheap calculation.
Rule of Thumb: If the calculation takes less than 1ms or doesn't involve passing objects/arrays to memoized child components, you probably don't need useMemo.