ReactuseMemo vs useCallback

useMemo vs useCallback

useMemo and useCallback are sibling performance hooks — both cache something between renders and both use a dependency array. The difference is simple: useMemo caches a value, useCallback caches a function. In fact, useCallback is just syntactic sugar for a specific use of useMemo.

The Fundamental Equivalence

JSX
// These two are exactly equivalent:
const memoizedFn = useCallback(fn, deps)
const memoizedFn = useMemo(() => fn, deps)

// useCallback is shorthand for "memoize this function reference"
// useMemo is the general form: "memoize whatever this factory returns"

React's source code implements useCallback(fn, deps) as useMemo(() => fn, deps). The distinction is entirely about clarity of intent: when you see useCallback, you know the author is memoizing a function; when you see useMemo, you know they are memoizing a computed value.

useMemo: Memoizing a Computed Value

useMemo is designed for cases where you need to cache the result of calling a function. The factory function runs and its return value is stored.

JSX
import { useMemo } from 'react'

function ProductSearch({ products, query, minPrice }) {
  // useMemo caches the *result* (the filtered array)
  const results = useMemo(() => {
    return products
      .filter(p => p.name.toLowerCase().includes(query.toLowerCase()))
      .filter(p => p.price >= minPrice)
      .sort((a, b) => a.price - b.price)
  }, [products, query, minPrice])

  return <ul>{results.map(p => <li key={p.id}>{p.name} — ${p.price}</li>)}</ul>
}
useCallback: Memoizing a Function

useCallback is designed for cases where you need to cache a function reference — not its return value — so that the reference stays stable across renders.

JSX
import { useCallback } from 'react'

function ProductSearch({ products, query, minPrice, onSelect }) {
  // useCallback caches the *function itself* (stable reference)
  const handleSelect = useCallback((product) => {
    console.log('Selected:', product)
    onSelect(product)
  }, [onSelect])

  // 'handleSelect' is the same reference as long as 'onSelect' is the same
  return <ProductList onSelect={handleSelect} />
}
The Same Problem — Solved with Each

Consider a DataGrid that shows a sorted, filtered table. It has an expensive Row child. We need to:

  • Memoize the sorted and filtered data (computed value) → useMemo

  • Memoize the row click handler (function) → useCallback

JSX
import { useState, useMemo, useCallback, memo } from 'react'

const Row = memo(function Row({ item, onSelect }) {
  return (
    <tr onClick={() => onSelect(item)}>
      <td>{item.name}</td>
      <td>{item.value}</td>
    </tr>
  )
})

function DataGrid({ data, filter, onItemSelect }) {
  const [sortKey, setSortKey] = useState('name')

  // ✅ useMemo — caches the computed/transformed array (a value)
  const processedData = useMemo(() => {
    const filtered = data.filter(item =>
      item.name.toLowerCase().includes(filter.toLowerCase())
    )
    return [...filtered].sort((a, b) => {
      if (a[sortKey] < b[sortKey]) return -1
      if (a[sortKey] > b[sortKey]) return 1
      return 0
    })
  }, [data, filter, sortKey]) // recompute when any of these change

  // ✅ useCallback — caches the function reference (so Row.memo works)
  const handleSelect = useCallback((item) => {
    console.log('Row clicked:', item.id)
    onItemSelect(item)
  }, [onItemSelect]) // only changes when onItemSelect changes

  return (
    <div>
      <button onClick={() => setSortKey('name')}>Sort by Name</button>
      <button onClick={() => setSortKey('value')}>Sort by Value</button>
      <table>
        <tbody>
          {processedData.map(item => (
            <Row key={item.id} item={item} onSelect={handleSelect} />
          ))}
        </tbody>
      </table>
    </div>
  )
}
The Decision Table

What you want to cache

Hook to use

What gets stored

Result of filtering/sorting a list

useMemo

The filtered/sorted array

Result of a calculation

useMemo

The computed number/string/object

An object/array passed as a stable prop

useMemo

The object/array

An event handler for a React.memo child

useCallback

The function itself

A function used in useEffect deps

useCallback

The function itself

A ref callback

useCallback

The function itself

Cost of Memoization

Both hooks have a non-zero overhead on every render:

  • Memory allocation to store the cached value/function

  • Dependency comparison on every render (using Object.is)

  • Cache invalidation logic managed by React

For small arrays, simple functions, and infrequently-rendered components, the overhead of memoization often exceeds the savings.

Warning
Memoization is an **optimization**, not a correctness tool. Your component should produce the correct result with or without it. If removing `useMemo` breaks your app, you have a bug — not a performance problem.
The Golden Rule

Before adding either hook, ask yourself three questions:

  • Have I measured a performance problem? Use the React Profiler to confirm the component is actually slow.

  • Is this the bottleneck? The Profiler shows which components are expensive and why.

  • Will memoization actually help? Only if the computation is truly expensive (useMemo) or the recipient is wrapped with React.memo (useCallback).

Note
The React team's official guidance: "Only optimize when you have a concrete performance problem to solve. Premature optimization is the root of all evil — even in React." Profile first, then optimize.
Quick Reference

JSX
// useMemo: cache a VALUE
const expensiveResult = useMemo(() => computeExpensiveValue(a, b), [a, b])
const stableObject = useMemo(() => ({ x: a, y: b }), [a, b])
const filteredList = useMemo(() => items.filter(pred), [items, pred])

// useCallback: cache a FUNCTION
const stableHandler = useCallback((arg) => doSomething(arg, dep), [dep])
const stableFetcher = useCallback(() => fetch(url), [url])

// Remember: useCallback(fn, deps) === useMemo(() => fn, deps)
Tip
When you are unsure which to use, think about what the consumer needs. If it needs the *result of calling the function* (a list, a number, an object), use `useMemo`. If it needs the *function itself* (to call later, to store in a ref, to pass as a prop), use `useCallback`.