Back to React — Another Look at useMemo and useCallback

E.Y.
4 min readNov 26, 2020
Photo by Paolo Chiabrando on Unsplash

Not long ago, I spent longer than I expected time on refactoring a piece of code that ends up utilising react.useMemo() . I blamed it on my false assumption that I understood react.useMemo() and react.useCallback() well enough, which apparently was not the case. So in this blog, I will take another look at the two hooks again, with some well-explained distinctions and well-supplied examples.

So what are useMemo and useCallback hook used for? Well it’s easy to say that useMemo is to memoize a value between renders and useCallback is to memoize a function call between renders.

Referential equality

Still sounds confusing? So am I! So let’s talk about referential equality in React first.

In JavaScript it’s easy to compare Primitive values to each other, like booleans, numbers, and strings. But when it comes to objects, we often see:

{} === {} // false
{"a"} === {"a"} // false
[] === [] // false
() => {} === () => {} // false

This is because for an object to equal another object, we need to make sure both object have same reference.

In React, whenever we define an object in the component, it won’t be the same one (referentially equal) to the last time itself when the component re-renders.

This can be annoying especially with computationally expensive calculations.

You may say this can potentially be fixed with useEffect :

const Bar = ({bar})=> {
const deps = {bar}
useEffect(() => {
expensive_calc(deps)
}, [deps])
return <></>
}
const Foo = () =>
const bar = 3
<Bar bar={bar}/>

We can see whenever bar gets passed from Foo to Bar the expensive_calc inside the useEffect hook needs to re-do the calculation. That’s because useEffect is going to do a referential equality check on deps whenever it renders, and it will always evaluate to true. But we want to calculate again if the bar value changes.

If bar is a primitive value, we can avoid this happening by changing the depend list:

useEffect(() => {
expensive_calc(deps)
}, [bar])

But what if it is not? Like an array, a function or a nested object? We can use useCallback and useMemo .

const Foo = () => {
const bar = useCallback(() => 3, [])
// const bar = useMemo(() => {()=>3}, [])
// rewrite using useMemo instead
<Bar bar={bar} />
}

Now the expensive_cal will only change when bar value changes thanks to the memoized value.

A close comparison

You can also see that the signature of useMemo and useCallback is very similar, and that they can convert to each other. They both take in a function and an array of dependencies.

useCallback(fn, deps);
useMemo(fn, deps);

To convert to each other:

useCallback(fn, deps) == useMemo(() => fn, deps)

Still, difference exist in that useMemo gives the referential equality between renders for values, and useCallback gives referential equality between renders for functions. useCallback returns the function uncalled while useMemo calls the function and gets the value for you.

const foo = () => "foo"const memoizedCb = useCallback(foo, []);
const memoizedMm = useMemo(foo, []);
memoizedCb(); // 'foo'
memoizedMm; // 'foo'

In a way,useCallback is just a special use case of useMemo , as the former can only takes a function, but the latter can take any value including functions.

Use cases

So what are the use cases for both hooks and what are the ones best suited for each?

In general, they are both suited whenever there’s checking for referential equality.

But there are slight differences. useMemo for really heavy calculations — e.g. heavy filtering, recursive calculations.

In case of lazy loading, both can do the same purpose. In the example below, the Child component will skip rendering if argument child remains the same.

function Parent(child) {
const child = useMemo(() => <Child item={child} />, [child]);

return (
<>
{child}
</>
)
}

For useCallback , it more focuses on inline function call like onClick={() => { doSomething(...); }. For example the component Child below will gets re-rendered each time the parent is re-rendered — because the inline functiondoSomethingInLine is recreated with a different reference each time:

function Parent({ ... }) {
const [child, setChild] = useState(0);

return (
<Child onChange={() => { doSomethingInLine(child); }} />
);
}

To adduseCallback:

function Parent({ ... }) {
const [child, setChild] = useState(0);
const addCb = useCallback(() => {doSomethingInLine(child);}, [child]);
...
return (
...
<Child onChange={addCb} />
);
}

So you can see if you have a child component that takes a function prop from its parent and with every re-render of its parent, the child need to re-render due to the recreation of its function prop. You can fix it by wrapping the function prop in a useCallback hook. So now the child will only rerender when there is a dependency change.

Note that the performance boost is not free. They ALWAYS come with a price for React to save the result/callback to the memory and do a comparison each time.

And if you do need to use them. Try not to omit the dependency list argument as otherwise the value will never be memoized.

That’s so much of it today!

Happy Reading!

--

--