eolas/neuron/bf894b27-4b56-45c5-b936-59d1518a7e06/Memoization_in_React.md

146 lines
4.9 KiB
Markdown
Raw Normal View History

2024-11-17 15:37:31 +00:00
---
tags:
- javascript
- react
---
# Memoization with useCallback and useMemo
## Rationale
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.
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.
If one part re-renders, it re-renders the entire component tree. `useCallback`
and `useMemo` can be used reduce this performance impact.
## useCallback
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.
`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.
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.
> Given the same dependency value, the `useCallback` hook returns the same
> function instance between renderings (aka memoization).
This said, for small functions that are not intensive, it doesn't really matter
if they are not memoized.
### Syntax
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.
```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));
},
[blockId, project_id]
);
```
Note that the syntax is similar to [React_useEffect](React_useEffect.md): there is a
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.
### Reference versus result
`useCallback` only memoizes the function _object_ (the reference) not the value
that is _returned_ by the function (the result).
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.
```jsx
function ParentComponent() {
const onHandleClick = useCallback(() => {
const special = calculatePi();
});
return <SubComponent handleClick={onHandleClick} />;
}
```
### Use cases
You should not apply `useCallback` in a blanket fashion, this can reduce
performance rather than improve it. The best scenarios are:
1. A functional component wrapped inside React.memo() accepts a function object
prop
2. When the function object is a dependency to other hooks, e.g.
`useEffect(..., [callback])`
3. When the function has some internal state, e.g. when the function is
debounced or throttled.
## useMemo
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.
> 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 1s through the function, it
> wont add them up; it will just remember the answer is 2 without executing the
> adding function.
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.
As with `useCallback`, `useMemo` is best applied to complex functions that are
computationally expensive, it shouldn't be used indiscriminately.
It should not be used with asynchronous functions, in these instances,
`useEffect` would be a better choice.
### Syntax
```jsx
const List = React.useMemo(
() =>
listOfItems.map((item) => ({
...item,
itemProp1: expensiveFunction(props.first),
itemProp2: anotherPriceyFunction(props.second),
})),
[listOfItems]
);
```