Articles

useMemo vs useCallback: When to Use Each in React

useMemo caches a computed value; useCallback caches a function reference. Both skip work on re-render, but they memoize different things.

Takina Takina · · 4 min read
Code editor showing React components

useMemo caches the result of a computation across re-renders; useCallback caches a function reference across re-renders. Both exist to skip unnecessary work when a component re-renders, and both take a dependency array that determines when the cached value is thrown away and recomputed — the difference is entirely in what gets cached, not how the caching mechanism works.

The problem both hooks solve

Every time a React component re-renders, every value and function defined in its body gets recreated from scratch, including ones whose inputs haven’t actually changed. Most of the time this is cheap enough not to matter. It becomes a real cost in two specific situations: an expensive computation — filtering a large array, running a heavy calculation — gets redone on every render even though its inputs are identical, or a function passed as a prop to a child gets a new reference on every render, which defeats React.memo on that child and causes it to re-render regardless of whether anything it actually depends on changed.

Both hooks are a form of memoization: trading a small amount of memory (storing the last result) for skipping recomputation when nothing relevant changed. Neither is unique to React as a concept, but React’s rendering model — where a component function reruns in full on every render — is what makes this particular flavor of memoization matter as much as it does.

useMemo: caching a value

useMemo takes a function and a dependency array, and returns the cached result of calling that function, only recalculating it when one of the dependencies changes between renders:

const sortedItems = useMemo(() => {
  return items.slice().sort((a, b) => a.price - b.price);
}, [items]);

Without useMemo, this sort would rerun on every render of the component, even ones triggered by something entirely unrelated, like a sibling state update. With it, the sort only reruns when items itself changes. The value returned — the sorted array — is what gets cached; the function passed to useMemo is just the recipe for producing it.

useCallback: caching a function reference

useCallback takes a function and a dependency array, and returns that same function reference across renders as long as the dependencies haven’t changed:

const handleSubmit = useCallback((formData) => {
  submitOrder(id, formData);
}, [id]);

This matters specifically when that function is passed down as a prop. If ChildForm is wrapped in React.memo, it will only skip re-rendering if all of its props are referentially equal to the previous render’s props — and a plain inline function, (formData) => submitOrder(id, formData), is a brand-new function object every render, which breaks that equality check even though the function does exactly the same thing each time. useCallback keeps the reference stable so React.memo on the child actually has a chance to work.

Under the hood, useCallback(fn, deps) is functionally equivalent to useMemo(() => fn, deps) — it’s useMemo specialized for the one case of memoizing a function itself rather than a computed value.

Side by side

useMemouseCallback
CachesA computed valueA function reference
Typical useExpensive calculations, derived data, referentially-stable objects passed to childrenEvent handlers or callbacks passed to memoized children
SignatureuseMemo(() => value, deps)useCallback(fn, deps)
Equivalent toItselfuseMemo(() => fn, deps)
SkipsRecomputationFunction recreation (and downstream re-renders it would cause)

When neither one is worth it

Both hooks have a cost of their own: React has to store the dependency array, compare it against the previous render’s array, and hold onto the cached value or function. For a cheap computation or a function that isn’t passed to a memoized child or used as a dependency elsewhere, that bookkeeping overhead can exceed whatever it’s saving — memoizing everything by default tends to make components harder to read without a measurable performance benefit.

The useful heuristic is to reach for either hook when there’s a concrete reason: a computation that’s visibly expensive (sorting or filtering a large list, a nontrivial calculation run on every keystroke), or a function passed to a child wrapped in React.memo, or a value used as a dependency in another hook’s dependency array, where an unstable reference would cause that other hook to fire more often than necessary. Reaching for a profiler to confirm a re-render is actually causing a perceptible slowdown is generally a better starting point than memoizing preemptively.

How this fits the bigger picture

Both hooks are about avoiding unnecessary work within a component that’s already re-rendering — they don’t stop the re-render itself. React.memo is the piece that skips a child’s re-render entirely when its props haven’t meaningfully changed, and useCallback and useMemo are what make that comparison actually succeed for function and object props, which are otherwise recreated fresh every render. This is a different layer of optimization than React Server Components, which reduce work by moving rendering to the server in the first place, or the kind of derived-state patterns that libraries built on signals use to skip whole-component re-renders rather than memoizing pieces of them.

The takeaway

useMemo caches a computed value; useCallback caches a function reference — the same underlying mechanism aimed at two different kinds of output. Reach for useMemo when a computation is genuinely expensive and its inputs rarely change, and for useCallback when a function is passed to a child wrapped in React.memo or used as another hook’s dependency. Outside those specific cases, the bookkeeping cost of memoizing usually isn’t worth paying — measure before reaching for either.

Takina Takina · · 5 min read

React Server Components, Explained Without the Hype

React Server Components render exclusively on the server and stream a serialized result — no client JS shipped for that component. Here's what that actually means.

#React #Web Development #JavaScript
Takina Takina · · 4 min read

Structural Typing vs Nominal Typing in TypeScript

Structural typing checks shape, not name — TypeScript treats two differently-named types as compatible if their members match, unlike nominal systems.

#TypeScript #JavaScript #Web Development