useCallback vs. useMemo
What is the difference between useCallBack
and useMemo
? And why do useMemo
and useCallback
expect a function? If you’ve worked with React Hooks, you might have asked yourself these questions.
We will take a look at how they are distinct from another.
Note: This article assumes a basic understanding of Hooks. You should have read “Hooks at a Glance.”
Abstract
The React docs say that useCallback:
Returns a memoized callback.
And that useMemo:
Returns a memoized value.
In other words, useCallback
gives you referential equality between renders for functions. And useMemo
gives you referential equality between renders for values.
useCallback
and useMemo
both expect a function and an array of dependencies. The difference is that useCallback
returns its function when the dependencies change while useMemo
calls its function and returns the result.
Since JavaScript has first-class functions, useCallback(fn, deps)
is equivalent to useMemo(() => fn, deps)
.
Translation
Let’s understand and explain the abstract description above using simple examples. I’ll go over everything, but feel free to skip what you already know.
First-class functions
We said that in JavaScript functions are first-class. This means in JavaScript you can assign a function to a value.
Storing functions in variables enables you to use them as:
- arguments to other functions,
- return values from functions,
- values to an object’s keys,
- values in an array,
- even as keys to the Map object.
Functions that return functions or take functions as input are called Higher-Order Functions.
Referential equality
If you use JavaScript, you probably know that there are two equality comparison operators: ==
for abstract equality and ===
for strict equality. JavaScript’s equality can be weird, and there are many great articles already written about this topic. Go read those for an in-depth look as I will only cover the basic cases relevant for useCallback
and useMemo
.
Referential equality uses strict equality comparison which is true if the operands are of the same type and the contents match.
Notice how foo === sameFoo
returns false
(contrary to greeting === otherGreeting
). This is because:
- two distinct objects are never equal for either strict or abstract comparisons,
- an expression comparing objects is only true if the operands reference the same object, and
- functions are objects in JavaScript.
foo
and sameFoo
have the same definition but reference two different objects.
Memoization
Memoization is a way to speed up performance by cutting down on computations.
When you first calculate a result, you save it. If you need the result for the same arguments again, you use the saved one instead of recalculating.
Here is a simple memoize
function in JavaScript.
In the example above, you save the result of adding 2 and 3. For each call after the first, the cached result is used.
API
The APIs of useCallback
and useMemo
look similar. They both take in a function and an array of dependencies.
useCallback(fn, deps);
useMemo(fn, deps);
So what is the difference? useCallback
returns its function uncalled so you can call it later, while useMemo
calls its function and returns the result.
Side note: Now - knowing about first class functions - you should understand why useCallback(fn, deps)
is equivalent to useMemo(() => fn, deps)
.
In the real world, the examples above are meaningless. I just gave it to help you understand the API. If you can pass a function to useCallback
or useMemo
directly and you can use that function with empty dependencies, you could define the function outside of the component (and do without useCallback
or useMemo
). Therefore, in the real world, you will see something like this.
useCallback
usually takes in an inline callback that calls the function that uses the dependencies. And useMemo
takes in a “create function” that calls some function and returns its results.
Another side note: The following might be trivial for you, but I had to think about it a long time to get it. The following code is wrong. You can’t use useCallback
to memoize values. Meaning useCallback(fn(), deps)
isn’t a thing.
Whenever you change name
sum
recalculates. useCallback
and useMemo
are so useful because they allow lazy evaluation. result
is evaluated eagerly on every render. Try it out and take a look at your console.
So why do we need two Hooks dedicated to memoizing values?
Usage
You want to use useCallback
or useMemo
whenever you depend on referential equality between renders. I find myself mostly using it for useEffect
, React.memo
and useMemo
to replace shouldComponentUpdate
from React.PureComponent
because the dependencies of these hooks get checked for referential equality.
Let’s say you have a component that given a userId
displays the user’s data.
Can you spot the mistake(s)?
If you have eslint-plugin-react-hooks
installed, it will yell at you for omitting fetchUser
from useEffect
’s dependencies. But mindlessly adding it will actually create an infinite loop!
In React functions defined in function components get re-created on every render because of closure, which results in referential inequality.
There are two ways to solve this issue. Admittedly the most elegant would be to move fetchUser
inside useEffect
and add userId
as a dependency.
But, this is an article about useCallback
and useMemo
, so let’s solve the problem using the former. (And, maybe you would like to use fetchUser
function in multiple places.)
You can define fetchUser
with useCallback
so that the function stays the same, unless userId
changes.
And lastly, let’s say you filter all users and display them in a list.
This is inefficient. We can use useMemo
to only recalculate the filtered users, when the query changes.