2022-07-13 15:20:04 +01:00
|
|
|
|
---
|
|
|
|
|
tags:
|
|
|
|
|
- javascript
|
|
|
|
|
- react
|
|
|
|
|
- react-hooks
|
|
|
|
|
---
|
|
|
|
|
|
2023-02-14 15:37:40 +00:00
|
|
|
|
# Memoization with useCallback and useMemo
|
2022-07-13 15:20:04 +01:00
|
|
|
|
|
|
|
|
|
## Rationale
|
|
|
|
|
|
2024-02-02 15:58:13 +00:00
|
|
|
|
In the lifecycle of a component, React re-renders the component when an update
|
|
|
|
|
is made. When React checks for any changes in a component, it may detect an
|
|
|
|
|
unintended or unexpected change due to how JavaScript handles equality and
|
|
|
|
|
shallow comparisons. This change in the React application will cause it to
|
|
|
|
|
re-render unnecessarily.
|
2022-07-13 15:20:04 +01:00
|
|
|
|
|
2024-02-02 15:58:13 +00:00
|
|
|
|
Additionally, if that re-rendering is an expensive operation, like a long for
|
|
|
|
|
loop, it can hurt performance. Expensive operations can be costly in either
|
|
|
|
|
time, memory, or processing. In addition to potential technical issues, this may
|
|
|
|
|
lead to poor user experience.
|
2022-07-13 15:20:04 +01:00
|
|
|
|
|
2024-02-02 15:58:13 +00:00
|
|
|
|
If one part re-renders, it re-renders the entire component tree. `useCallback`
|
|
|
|
|
and `useMemo` can be used reduce this performance impact.
|
2022-07-13 15:20:04 +01:00
|
|
|
|
|
|
|
|
|
## useCallback
|
|
|
|
|
|
2024-02-02 15:58:13 +00:00
|
|
|
|
The `useCallback` hook is used to wrap functions. It tells React to not
|
|
|
|
|
re-create a wrapped function when a component re-renders, unless any of the
|
|
|
|
|
useCallback's dependencies change.
|
2022-07-13 15:20:04 +01:00
|
|
|
|
|
2024-02-02 15:58:13 +00:00
|
|
|
|
`useCallback` returns a memoized version of the callback function it is passed.
|
|
|
|
|
This means that the function object returned from useCallback will be the same
|
|
|
|
|
between re-renders.
|
2022-07-13 15:20:04 +01:00
|
|
|
|
|
2024-02-02 15:58:13 +00:00
|
|
|
|
Remember that in JavaScript, functions are objects and components are functions.
|
|
|
|
|
As a result, every time a component containing a function re-renders, it create
|
|
|
|
|
a new instance of the function in memory.
|
2022-07-13 15:20:04 +01:00
|
|
|
|
|
2024-02-02 15:58:13 +00:00
|
|
|
|
> Given the same dependency value, the `useCallback` hook returns the same
|
|
|
|
|
> function instance between renderings (aka memoization).
|
2022-07-13 15:20:04 +01:00
|
|
|
|
|
2024-02-02 15:58:13 +00:00
|
|
|
|
This said, for small functions that are not intensive, it doesn't really matter
|
|
|
|
|
if they are not memoized.
|
2022-07-13 15:20:04 +01:00
|
|
|
|
|
|
|
|
|
### Syntax
|
|
|
|
|
|
2024-02-02 15:58:13 +00:00
|
|
|
|
A standard case of this would be a function that runs on a button click, for
|
|
|
|
|
instance when sending data from a form to a server. In the example below there
|
|
|
|
|
is quite a lot going on, and most of it is independent of the actual UI-cycle of
|
|
|
|
|
the component.
|
2022-07-13 15:20:04 +01:00
|
|
|
|
|
|
|
|
|
```jsx
|
|
|
|
|
const handleSubmit = useCallback(
|
|
|
|
|
async (formValues) => {
|
|
|
|
|
setPendSaveConfig(true);
|
|
|
|
|
const payload = new GenerateConfig({
|
|
|
|
|
workflowId: project_id,
|
|
|
|
|
blockId: blockId,
|
|
|
|
|
config: formValues,
|
|
|
|
|
});
|
|
|
|
|
axios
|
|
|
|
|
.post(`${process.env.REACT_APP_ENDPOINT}/save-block-config`, payload)
|
|
|
|
|
.then((res) => console.log(res))
|
|
|
|
|
.finally(() => setPendSaveConfig(false))
|
|
|
|
|
.catch((err) => console.error(err));
|
|
|
|
|
},
|
2023-02-10 18:22:04 +00:00
|
|
|
|
[blockId, project_id]
|
2022-07-13 15:20:04 +01:00
|
|
|
|
);
|
|
|
|
|
```
|
|
|
|
|
|
2024-02-17 11:57:44 +00:00
|
|
|
|
Note that the syntax is similar to [useEffect](useEffect.md): there is a
|
2024-02-02 15:58:13 +00:00
|
|
|
|
dependency array. The effect is the same: the function contained within
|
|
|
|
|
`useCallback` will only re-rendered if one of these dependencies changes.
|
|
|
|
|
However (see next section) the function will run in its memoized form on every
|
|
|
|
|
click.
|
2022-07-13 15:20:04 +01:00
|
|
|
|
|
|
|
|
|
### Reference versus result
|
|
|
|
|
|
2024-02-02 15:58:13 +00:00
|
|
|
|
`useCallback` only memoizes the function _object_ (the reference) not the value
|
|
|
|
|
that is _returned_ by the function (the result).
|
2022-07-13 15:20:04 +01:00
|
|
|
|
|
2024-02-02 15:58:13 +00:00
|
|
|
|
In the example below, the `calculatePi()` function reference will not change
|
|
|
|
|
between renders but each time the click event fires, the value returned by
|
|
|
|
|
`calculatePi()` will change. In other words, it will be assigned fresh memory.
|
2022-07-13 15:20:04 +01:00
|
|
|
|
|
|
|
|
|
```jsx
|
|
|
|
|
function ParentComponent() {
|
|
|
|
|
const onHandleClick = useCallback(() => {
|
|
|
|
|
const special = calculatePi();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return <SubComponent handleClick={onHandleClick} />;
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### Use cases
|
|
|
|
|
|
2024-02-02 15:58:13 +00:00
|
|
|
|
You should not apply `useCallback` in a blanket fashion, this can reduce
|
|
|
|
|
performance rather than improve it. The best scenarios are:
|
2022-07-13 15:20:04 +01:00
|
|
|
|
|
2024-02-02 15:58:13 +00:00
|
|
|
|
1. A functional component wrapped inside React.memo() accepts a function object
|
|
|
|
|
prop
|
2022-07-13 15:20:04 +01:00
|
|
|
|
|
2024-02-02 15:58:13 +00:00
|
|
|
|
2. When the function object is a dependency to other hooks, e.g.
|
|
|
|
|
`useEffect(..., [callback])`
|
2022-07-13 15:20:04 +01:00
|
|
|
|
|
2024-02-02 15:58:13 +00:00
|
|
|
|
3. When the function has some internal state, e.g. when the function is
|
|
|
|
|
debounced or throttled.
|
2022-07-13 15:20:04 +01:00
|
|
|
|
|
|
|
|
|
## useMemo
|
|
|
|
|
|
2024-02-02 15:58:13 +00:00
|
|
|
|
We can think of `useMemo` as a complement to `useCallback`. The main difference
|
|
|
|
|
is that whilst `useCallback` memoizes the function reference, `useMemo` memoizes
|
|
|
|
|
the function result; the value that is returned.
|
2022-07-13 15:20:04 +01:00
|
|
|
|
|
2024-02-02 15:58:13 +00:00
|
|
|
|
> In memoization, the result is “remembered” when the same parameters are
|
|
|
|
|
> passed-in subsequently. If we have a function compute 1 + 1, it will return 2.
|
|
|
|
|
> But if it uses memoization, the next time we run 1’s through the function, it
|
|
|
|
|
> won’t add them up; it will just remember the answer is 2 without executing the
|
|
|
|
|
> adding function.
|
2022-07-13 15:20:04 +01:00
|
|
|
|
|
2024-02-02 15:58:13 +00:00
|
|
|
|
Like `useCallback`, `useMemo` takes a function and an array of dependencies for
|
|
|
|
|
the same reason: if one of the dependencies changes then the function will
|
|
|
|
|
recalculate (and re-memoize) the result but they don't the same memoized value
|
|
|
|
|
will be returned.
|
2022-07-13 15:20:04 +01:00
|
|
|
|
|
2024-02-02 15:58:13 +00:00
|
|
|
|
As with `useCallback`, `useMemo` is best applied to complex functions that are
|
|
|
|
|
computationally expensive, it shouldn't be used indiscriminately.
|
2022-07-13 15:20:04 +01:00
|
|
|
|
|
2024-02-02 15:58:13 +00:00
|
|
|
|
It should not be used with asynchronous functions, in these instances,
|
|
|
|
|
`useEffect` would be a better choice.
|
2022-07-13 15:20:04 +01:00
|
|
|
|
|
|
|
|
|
### Syntax
|
|
|
|
|
|
|
|
|
|
```jsx
|
|
|
|
|
const List = React.useMemo(
|
|
|
|
|
() =>
|
|
|
|
|
listOfItems.map((item) => ({
|
|
|
|
|
...item,
|
|
|
|
|
itemProp1: expensiveFunction(props.first),
|
|
|
|
|
itemProp2: anotherPriceyFunction(props.second),
|
|
|
|
|
})),
|
2023-02-10 18:22:04 +00:00
|
|
|
|
[listOfItems]
|
2022-07-13 15:20:04 +01:00
|
|
|
|
);
|
|
|
|
|
```
|