useMemo vs useCallback
Answer
useMemo and useCallback are React hooks for memoization. They cache values and functions to prevent unnecessary recalculations and re-renders.
Key Difference
// useMemo - caches a VALUE
const memoizedValue = useMemo(() => {
return expensiveCalculation(a, b);
}, [a, b]);
// useCallback - caches a FUNCTION
const memoizedCallback = useCallback(() => {
doSomething(a, b);
}, [a, b]);
// They're related:
// useCallback(fn, deps) is equivalent to useMemo(() => fn, deps)
useMemo for Expensive Calculations
function ProductList({ products, filter }) {
// ❌ Without useMemo - recalculates every render
const filteredProducts = products.filter((p) => p.name.includes(filter));
// ✅ With useMemo - only recalculates when deps change
const filteredProducts = useMemo(() => {
console.log("Filtering products...");
return products.filter((p) => p.name.includes(filter));
}, [products, filter]);
return (
<ul>
{filteredProducts.map((p) => (
<li key={p.id}>{p.name}</li>
))}
</ul>
);
}
useCallback for Stable References
function Parent() {
const [count, setCount] = useState(0);
// ❌ Without useCallback - new function every render
const handleClick = () => {
console.log("Clicked!");
};
// Child re-renders even though handleClick logic is same
// ✅ With useCallback - stable function reference
const handleClick = useCallback(() => {
console.log("Clicked!");
}, []);
// Child only re-renders if function actually changes
return (
<div>
<button onClick={() => setCount((c) => c + 1)}>Count: {count}</button>
<ExpensiveChild onClick={handleClick} />
</div>
);
}
// Memoized child component
const ExpensiveChild = React.memo(({ onClick }) => {
console.log("Child rendered");
return <button onClick={onClick}>Click me</button>;
});
When They Help
// 1. Passing callbacks to memoized children
const MemoizedChild = React.memo(Child);
function Parent() {
const handleSubmit = useCallback((data) => {
submitForm(data);
}, []);
return <MemoizedChild onSubmit={handleSubmit} />;
}
// 2. Dependencies in other hooks
function SearchResults({ query }) {
const params = useMemo(() => ({ query, limit: 10 }), [query]);
useEffect(() => {
fetchResults(params);
}, [params]); // Won't refetch every render
}
// 3. Expensive computations
function DataGrid({ data }) {
const sortedData = useMemo(() => {
return [...data].sort((a, b) => a.value - b.value);
}, [data]);
const statistics = useMemo(() => {
return {
sum: data.reduce((acc, item) => acc + item.value, 0),
avg: data.reduce((acc, item) => acc + item.value, 0) / data.length,
max: Math.max(...data.map((item) => item.value)),
};
}, [data]);
}
When NOT to Use
// ❌ Simple calculations - overhead not worth it
const doubled = useMemo(() => count * 2, [count]);
// Just do: const doubled = count * 2;
// ❌ Primitives don't need memoization
const message = useMemo(() => "Hello", []);
// Just do: const message = 'Hello';
// ❌ Memoizing without React.memo on child
function Parent() {
const handleClick = useCallback(() => {}, []);
return <Child onClick={handleClick} />; // Child not memoized!
}
// Child still re-renders unless it's wrapped in React.memo
Common Pattern
function TodoList() {
const [todos, setTodos] = useState([]);
const [filter, setFilter] = useState("all");
// Memoize filtered todos
const filteredTodos = useMemo(() => {
switch (filter) {
case "active":
return todos.filter((t) => !t.completed);
case "completed":
return todos.filter((t) => t.completed);
default:
return todos;
}
}, [todos, filter]);
// Memoize callbacks passed to children
const addTodo = useCallback((text) => {
setTodos((prev) => [...prev, { id: Date.now(), text, completed: false }]);
}, []);
const toggleTodo = useCallback((id) => {
setTodos((prev) =>
prev.map((t) => (t.id === id ? { ...t, completed: !t.completed } : t))
);
}, []);
return (
<>
<TodoInput onAdd={addTodo} />
<TodoFilter filter={filter} onFilterChange={setFilter} />
<TodoItems todos={filteredTodos} onToggle={toggleTodo} />
</>
);
}
Comparison Table
| Aspect | useMemo | useCallback |
|---|---|---|
| Returns | Any value | Function only |
| Use for | Expensive calculations | Stable callbacks |
| Pairs with | - | React.memo |
| Without it | Value recalculated | New function created |
Key Points
useMemocaches values,useCallbackcaches functions- Only useful when preventing re-renders or expensive calculations
- Must pair
useCallbackwithReact.memoon children to see benefit - Don't use for simple operations (premature optimization)
- Dependencies work same as
useEffect - Measure performance before and after optimizing