React useMemo and useCallback
useMemo and useCallback are performance hooks. They prevent expensive recalculations and unnecessary function re-creations between renders. Both are optimization tools — they solve real problems when used in the right situations.
The Problem These Hooks Solve
Every time a React component re-renders, it runs the entire function body again. This means every variable gets re-created, every expression re-evaluates, and every function gets a brand new reference.
For most values, this is fine. But for expensive computations or functions passed as props to child components, unnecessary recreation wastes resources and causes child components to re-render even when nothing relevant changed.
useMemo: Memoize a Computed Value
useMemo caches the result of a calculation. It only recomputes when one of its dependencies changes.
Without useMemo — recalculates on every render
function ProductList({ products, filterText }) {
// This runs on EVERY render, even if filterText didn't change
const filtered = products.filter(p => p.name.includes(filterText));
return <ul>{filtered.map(p => <li key={p.id}>{p.name}</li>)}</ul>;
}
With useMemo — recalculates only when filterText or products change
import { useMemo } from "react";
function ProductList({ products, filterText }) {
const filtered = useMemo(() => {
return products.filter(p => p.name.includes(filterText));
}, [products, filterText]);
return <ul>{filtered.map(p => <li key={p.id}>{p.name}</li>)}</ul>;
}
Diagram: useMemo as a result cache
Render 1: products=[...], filterText="laptop"
→ Run filter → Result = [Laptop A, Laptop B]
→ Cache the result
Render 2: products=[...], filterText="laptop" (SAME)
→ Dependencies unchanged → Return CACHED result
→ No filter runs!
Render 3: products=[...], filterText="phone" (CHANGED)
→ Dependency changed → Run filter again → New result
→ Update cache
useMemo Syntax
const memoizedValue = useMemo(() => {
return computeExpensiveValue(a, b);
}, [a, b]);
- First argument: a function that returns the value to cache
- Second argument: dependency array — recompute when these change
- Returns: the cached value
useCallback: Memoize a Function
useCallback caches a function itself (not its return value). Every render creates a brand new function reference. When you pass functions as props to child components, a new reference triggers unnecessary child re-renders.
Without useCallback
function Parent() {
const [count, setCount] = useState(0);
// New function reference every render!
const handleClick = () => {
console.log("clicked");
};
// Child re-renders because handleClick is "new" every time
return (
<div>
<button onClick={() => setCount(count + 1)}>Count: {count}</button>
<Child onClick={handleClick} />
</div>
);
}
With useCallback
import { useCallback } from "react";
function Parent() {
const [count, setCount] = useState(0);
// Same function reference across renders (unless dep changes)
const handleClick = useCallback(() => {
console.log("clicked");
}, []); // No dependencies = never recreated
return (
<div>
<button onClick={() => setCount(count + 1)}>Count: {count}</button>
<Child onClick={handleClick} />
</div>
);
}
useCallback Syntax
const memoizedFn = useCallback(() => {
doSomething(a, b);
}, [a, b]);
- First argument: the function to cache
- Second argument: dependency array — recreate function only when these change
- Returns: the cached function
useMemo vs useCallback
Hook What it caches When to use
---------------------------------------------------------
useMemo The RESULT of a fn Expensive calculations
useCallback The FUNCTION itself Functions passed as props
// useMemo
const result = useMemo(() => computeValue(), [dep]);
// result is the return value
// useCallback
const fn = useCallback(() => doWork(), [dep]);
// fn is the function itself (not called yet)
A quick mental shortcut: useMemo(() => fn(), deps) is equivalent to useCallback(fn, deps) — they are the same mechanism applied differently.
Practical Example: Search + Sort
function DataTable({ data, searchQuery, sortField }) {
// useMemo for expensive data transformation
const processedData = useMemo(() => {
return data
.filter(row => row.name.toLowerCase().includes(searchQuery.toLowerCase()))
.sort((a, b) => a[sortField] > b[sortField] ? 1 : -1);
}, [data, searchQuery, sortField]);
// useCallback for row click handler
const handleRowClick = useCallback((id) => {
console.log("Row clicked:", id);
}, []); // No deps — function never changes
return (
<table>
<tbody>
{processedData.map(row => (
<tr key={row.id} onClick={() => handleRowClick(row.id)}>
<td>{row.name}</td>
<td>{row.value}</td>
</tr>
))}
</tbody>
</table>
);
}
When NOT to Use These Hooks
Every use of useMemo or useCallback adds code complexity and has a small overhead for managing the cache. Do not use them:
- On every function or value by default
- For simple computations that run instantly
- When the component is simple and re-renders are infrequent
- Before you have identified a performance problem
The React team's own advice: measure first, then optimize. Use these hooks when you can prove there is a slowdown worth fixing.
React.memo: Memoize an Entire Component
React.memo wraps a component to prevent it from re-rendering unless its props actually change. It works well with useCallback — pass a stable function to a memoized child.
const Child = React.memo(function Child({ onClick }) {
console.log("Child rendered");
return <button onClick={onClick}>Click</button>;
});
Without React.memo, the child re-renders whenever the parent renders. With it, the child re-renders only when its onClick prop is a different reference — which useCallback in the parent prevents.
Summary
useMemo caches the result of an expensive calculation and recomputes only when dependencies change. useCallback caches a function reference so passing it as a prop to child components does not trigger unnecessary re-renders. Both share the same dependency array mechanism. Use these hooks when profiling shows clear performance problems — not preemptively on every function or computed value. Combine them with React.memo on child components for the greatest effect.
