Can we rely on useCallback as a semantic guarantee?

Ceci García García
Trabe
Published in
3 min readSep 30, 2019
Photo by Ewa Gillen on Unsplash

In Trabe, we’re working on migrating class components to functional components with Hooks. This week, we came across a particular problem when passing callbacks as a dependency of a useEffect Hook. Let’s see the problem with an example:

We need a <Counter /> that accepts a “notify” callback, and uses it to notify about its value changes:

The <Counter /> will notify about its value on the first render (the source gets the initial value), and on any subsequent changes.

The thing is, the source to notify may change because a new url is injected to <CounterNotifier />. When the url changes, since the <Counter /> is already mounted, the new source won’t receive the current value until a change happens (the effect is triggered).

To solve that, we may need to pass the notify callback itself as a dependency of the effect:

If the <Counter /> component notifies when the value or the notify callback changes, we have to ensure that the callback only changes when it really changes: when a new url is injected to <CounterNotifier />.

Memoizing a function with useCallback to ensure it doesn’t change

Wrapping the notify callback with a useCallback Hook sounds like the way to ensure it won’t change unless its dependencies change (the url):

Actually, the React docs describes this in a FAQ as the recommend way to pass a function as a Hook dependency when we need to.

But…

What about cache busting?

React docs also warn:

You may rely on useMemo as a performance optimization, not as a semantic guarantee. In the future, React may choose to “forget” some previously memoized values and recalculate them on next render, e.g. to free memory for offscreen components. Write your code so that it still works without useMemo — and then add it to optimize performance.

And also:

useCallback(fn, deps) is equivalent to useMemo(() => fn, deps).

That led us to think that useCallback shouldn’t be used as a semantic guarantee either.

Doing some investigation, we’ve found some people complaining about cache busting on useCallback and asking for a third argument where you can opt-out of this cache purging. We haven’t found any answer from the React team ensuring that the cache busting just affects useMemo, not useCallback.

The only thing we’ve found in the docs about this, is in the FAQ mentioned before:

You can add a function to effect dependencies but wrap its definition into the useCallback Hook. This ensures it doesn’t change on every render unless its own dependencies also change.

So… What should we think? 😅 I hope they may update the docs to make it clear.

Summing up

React team: We need a way to ensure that a callback that needs to be defined inside a functional component won’t change if we don’t want it to. If this way exists and it’s called useCallback, please tell us about the cache busting… Or maybe we’re losing something important, in that case:

--

--