What's the difference between useCallback and useMemo in practice?
ReactjsReactjs Problem Overview
Maybe I misunderstood something, but useCallback Hook runs everytime when re-render happens.
I passed inputs - as a second argument to useCallback - non-ever-changeable constants - but returned memoized callback still runs my expensive calculations at every render (I'm pretty sure - you can check by yourself in the snippet below).
I've changed useCallback to useMemo - and useMemo works as expected — runs when passed inputs changes. And really memoizes the expensive calculations.
Live example:
'use strict';
const { useState, useCallback, useMemo } = React;
const neverChange = 'I never change';
const oneSecond = 1000;
function App() {
const [second, setSecond] = useState(0);
// This 👇 expensive function executes everytime when render happens:
const calcCallback = useCallback(() => expensiveCalc('useCallback'), [neverChange]);
const computedCallback = calcCallback();
// This 👇 executes once
const computedMemo = useMemo(() => expensiveCalc('useMemo'), [neverChange]);
setTimeout(() => setSecond(second + 1), oneSecond);
return `
useCallback: ${computedCallback} times |
useMemo: ${computedMemo} |
App lifetime: ${second}sec.
`;
}
const tenThousand = 10 * 1000;
let expensiveCalcExecutedTimes = { 'useCallback': 0, 'useMemo': 0 };
function expensiveCalc(hook) {
let i = 0;
while (i < tenThousand) i++;
return ++expensiveCalcExecutedTimes[hook];
}
ReactDOM.render(
React.createElement(App),
document.querySelector('#app')
);
<h1>useCallback vs useMemo:</h1>
<div id="app">Loading...</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.3/umd/react-dom.production.min.js"></script>
Reactjs Solutions
Solution 1 - Reactjs
TL;DR;
useMemo
is to memoize a calculation result between a function's calls and between rendersuseCallback
is to memoize a callback itself (referential equality) between rendersuseRef
is to keep data between renders (updating does not fire re-rendering)useState
is to keep data between renders (updating will fire re-rendering)
Long version:
useMemo
focuses on avoiding heavy calculation.
useCallback
focuses on a different thing: it fixes performance issues when inline event handlers like onClick={() => { doSomething(...); }
cause PureComponent
child re-rendering (because function expressions there are referentially different each time)
This said, useCallback
is closer to useRef
, rather than a way to memoize a calculation result.
Looking into the docs I do agree it looks confusing there.
> useCallback
will return a memoized version of the callback that only changes if one of the inputs has changed. This is useful when passing callbacks to optimized child components that rely on reference equality to prevent unnecessary renders (e.g. shouldComponentUpdate).
Example
Suppose we have a PureComponent
-based child <Pure />
that would re-render only once its props
are changed.
This code re-renders the child each time the parent is re-rendered — because the inline function is referentially different each time:
function Parent({ ... }) {
const [a, setA] = useState(0);
...
return (
...
<Pure onChange={() => { doSomething(a); }} />
);
}
We can handle that with the help of useCallback
:
function Parent({ ... }) {
const [a, setA] = useState(0);
const onPureChange = useCallback(() => {doSomething(a);}, []);
...
return (
...
<Pure onChange={onPureChange} />
);
}
But once a
is changed we find that the onPureChange
handler function we created — and React remembered for us — still points to the old a
value! We've got a bug instead of a performance issue! This is because onPureChange
uses a closure to access the a
variable, which was captured when onPureChange
was declared. To fix this we need to let React know where to drop onPureChange
and re-create/remember (memoize) a new version that points to the correct data. We do so by adding a
as a dependency in the second argument to `useCallback :
const [a, setA] = useState(0);
const onPureChange = useCallback(() => {doSomething(a);}, [a]);
Now, if a
is changed, React re-renders the component. And during re-render, it sees that the dependency for onPureChange
is different, and there is a need to re-create/memoize a new version of the callback. Finally everything works!
NB not just for PureComponent
/React.memo
, referential equality may be critical when use something as a dependency in useEffect
.
Solution 2 - Reactjs
useMemo
and useCallback
use memoization.
I like to think of memoization as remembering something.
While both useMemo
and useCallback
remember something between renders until the dependancies change, the difference is just what they remember.
useMemo
will remember the returned value from your function.
useCallback
will remember your actual function.
Source: What is the difference between useMemo and useCallback?
Solution 3 - Reactjs
One-liner for useCallback
vs useMemo
:
> useCallback(fn, deps)
is equivalent to useMemo(() => fn, deps)
.
With useCallback
you memoize functions, useMemo
memoizes any computed value:
const fn = () => 42 // assuming expensive calculation here
const memoFn = useCallback(fn, [dep]) // (1)
const memoFnReturn = useMemo(fn, [dep]) // (2)
(1)
will return a memoized version of fn
- same reference across multiple renders, as long as dep
is the same. But every time you invoke memoFn
, that complex computation starts again.
(2)
will invoke fn
every time dep
changes and remember its returned value (42
here), which is then stored in memoFnReturn
.
const App = () => {
const [dep, setDep] = useState(0);
const fn = () => 42 + dep; // assuming expensive calculation here
const memoFn = useCallback(fn, [dep]); // (1)
const memoFnReturn = useMemo(fn, [dep]); // (2)
return (
<div>
<p> memoFn is {typeof memoFn} </p>
<p>
Every call starts new calculation, e.g. {memoFn()} {memoFn()}
</p>
<p>memoFnReturn is {memoFnReturn}</p>
<p>
Only one calculation for same dep, e.g. {memoFnReturn} {memoFnReturn}
</p>
<button onClick={() => setDep((p) => p + 1)}>Change dep</button>
</div>
);
}
ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.0/umd/react.production.min.js" integrity="sha256-32Gmw5rBDXyMjg/73FgpukoTZdMrxuYW7tj8adbN8z4=" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.0/umd/react-dom.production.min.js" integrity="sha256-bjQ42ac3EN0GqK40pC9gGi/YixvKyZ24qMP/9HiGW7w=" crossorigin="anonymous"></script>
<div id="root"></div>
<script>var { useReducer, useEffect, useState, useRef, useCallback, useMemo } = React</script>
Solution 4 - Reactjs
You are calling the memoized callback every time, when you do:
const calcCallback = useCallback(() => expensiveCalc('useCallback'), [neverChange]);
const computedCallback = calcCallback();
This is why the count of useCallback
is going up. However the function never changes, it never *creates a new callback, its always the same. Meaning useCallback
is correctly doing it's job.
Let's making some changes in your code to see this is true. Let's create a global variable, lastComputedCallback
, that will keep track of if a new (different) function is returned. If a new function is returned, that means useCallback
just "executed again". So when it executes again we will call expensiveCalc('useCallback')
, as this is how you are counting if useCallback
did work. I do this in the code below, and it is now clear that useCallback
is memoizing as expected.
If you want to see useCallback
re-create the function everytime, then uncomment the line in the array that passes second
. You will see it re-create the function.
'use strict';
const { useState, useCallback, useMemo } = React;
const neverChange = 'I never change';
const oneSecond = 1000;
let lastComputedCallback;
function App() {
const [second, setSecond] = useState(0);
// This 👇 is not expensive, and it will execute every render, this is fine, creating a function every render is about as cheap as setting a variable to true every render.
const computedCallback = useCallback(() => expensiveCalc('useCallback'), [
neverChange,
// second // uncomment this to make it return a new callback every second
]);
if (computedCallback !== lastComputedCallback) {
lastComputedCallback = computedCallback
// This 👇 executes everytime computedCallback is changed. Running this callback is expensive, that is true.
computedCallback();
}
// This 👇 executes once
const computedMemo = useMemo(() => expensiveCalc('useMemo'), [neverChange]);
setTimeout(() => setSecond(second + 1), oneSecond);
return `
useCallback: ${expensiveCalcExecutedTimes.useCallback} times |
useMemo: ${computedMemo} |
App lifetime: ${second}sec.
`;
}
const tenThousand = 10 * 1000;
let expensiveCalcExecutedTimes = { 'useCallback': 0, 'useMemo': 0 };
function expensiveCalc(hook) {
let i = 0;
while (i < 10000) i++;
return ++expensiveCalcExecutedTimes[hook];
}
ReactDOM.render(
React.createElement(App),
document.querySelector('#app')
);
<h1>useCallback vs useMemo:</h1>
<div id="app">Loading...</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.3/umd/react-dom.production.min.js"></script>
Benefit of useCallback
is that the function returned is the same, so react is not removeEventListener
'ing and addEventListener
ing on the element everytime, UNLESS the computedCallback
changes. And the computedCallback
only changes when the variables change. Thus react will only addEventListener
once.
Great question, I learned a lot by answering it.
Solution 5 - Reactjs
useCallback()
and useMemo()
are pretty much same but useCallback
saves the function reference in the memory and checks on the second render whether it's same or not if it is same then it returns last saved function without recreating it and if it is changed then it returns a new function and replaces it with older function in memory for future rendering.
useMemo
works in same manner but it can't save your function but the computed or returned value. On every render useMemo
checks the value if the returned value of your function is the same on second render then it will return same value without recalculating the function value and if the value is not same on second render then it will call the function and return new value and store it for future render.
NOTE: You have to be careful when you have need to use these hooks. Unnecessary use of these hooks can make your app performance worse because they use memory. Be sure if your component re-renders many times with heavy calculations then it is good to use these hooks.