Skip to main content

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

AspectuseMemouseCallback
ReturnsAny valueFunction only
Use forExpensive calculationsStable callbacks
Pairs with-React.memo
Without itValue recalculatedNew function created

Key Points

  • useMemo caches values, useCallback caches functions
  • Only useful when preventing re-renders or expensive calculations
  • Must pair useCallback with React.memo on children to see benefit
  • Don't use for simple operations (premature optimization)
  • Dependencies work same as useEffect
  • Measure performance before and after optimizing