React: What is useCallback Hook and When to Use It
One of the most important concepts to understand for optimizing React is memoization. Memoization is a performance optimization technique that eliminates the need to recompute a value for a given input by storing the original computation and returning that stored value when the same input is provided. Caching is a form of memoization.
React has three APIs for memoization: memo
, useMemo
, and useCallback
. The caching strategy React has adopted has a size of 1. Meaning that they only keep around the most recent value of the input and result.
There are specific reasons both useMemo
and useCallback
are built-in to React:
- Referential equality
- Computational expensive calculation
Consider the Blurb
component below:
function Foo({bar, baz}) {
const options = {bar, baz}
React.useEffect(() => {
buzz(options)
}, [options]) // we want this to re-run if bar or baz change
return <div>foobar</div>
}
function Blub() {
return <Foo bar="bar value" baz={3} />
}
This is problematic because useEffect
is going to do a referential equality check on options between every render, and thanks to the way JavaScript works, options
will be new every time so when React tests whether options
changed between renders it'll always evaluate to true
, meaning the useEffect
callback will be called after every render rather than only when bar
and baz
change.
We can use useCallback
to fix it, especially if bar
or baz
are non-primitive like objects/arrays/functios/etc:
function Foo({bar, baz}) {
React.useEffect(() => {
const options = {bar, baz}
buzz(options)
}, [bar, baz])
return <div>foobar</div>
}
function Blub() {
const bar = React.useCallback(() => {}, [])
const baz = React.useMemo(() => [1, 2, 3], [])
return <Foo bar={bar} baz={baz} />
}
The reason is because when you define an object inside your React function component, it is not going to be referentially equal to the last time that same object was defined (even if it has all the same properties with all the same values).
useMemo
and useCallback
The Difference Between useMemo
is to memoize a calculation result between a function's calls and between renders, Whereas useCallback
is to memoize a callback itself (referential equality) between renders.
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)
Pass an inline callback and an array of dependencies. useCallback
will return a memoized version of the callback that only changes if one of the dependencies has changed. This is useful when passing callbacks to optimized child components that rely on reference equality to prevent unnecessary renders.
This means useCallback(fn, deps)
is equivalent to useMemo(() => fn, deps)
.
Conclusion
useMemo
and useCallback
are performance optimizations. Use them only when you already have a performance problem instead of pre-emptively.
Also because some inline functions with useCallback
are not always more performant. For example:
const dispense = candy => {
setCandies(allCandies => allCandies.filter(c => c !== candy))
}
is more performant than
const dispense = candy => {
setCandies(allCandies => allCandies.filter(c => c !== candy))
}
const dispenseCallback = React.useCallback(dispense, [])
They're exactly the same, except the useCallback
version is doing more work. Not only do we have to define the function, but we also have to define an array ([]
) and call the React.useCallback
, which itself is setting properties/running through logical expressions etc.
React is VERY fast, and there are so many things you can do with with your time that would be better than optimizing stuff like this -- shipping your product, for example. Write your code so that it still works without memoization — and then add it to optimize performance once needed.