Use useCallback()
and useMemo()
efficiently
Functions defined inside function components are recreated each time the component is rendered, resulting in referential inequality. This causes these components to rerender, in some cases unnecessarily.
To prevent rerendering components, you can use the useCallback()
and
useMemo()
hooks. The useCallback()
hook returns a memoized callback to maintain referential equality between
renders of functions, and the useMemo()
hook returns a
memoized value to maintain referential equality between renders of
values.
Note that useCallback()
and useMemo()
can
result in more memory being allocated, so they must be used appropriately or
they may actually reduce performance.
When to use useCallback()
This section describes situations where it is desirable to use
useCallback()
:
- Avoiding rerendering a child component when a function is recreated.
- Preventing
useEffect()
from creating an infinite loop.
Avoid rerendering a child component when a function is recreated
If you have a parent component that passes a callback function to a child
component that uses React.memo()
, rerendering the
parent component recreates the function, which forces the child
component to rerender, despite it using
React.memo()
. To avoid rerendering the
child component, wrap the function with
useCallback()
. For example:
function Parent() {
const currentDate = 10/10/2020;
const someFn = () => {
...
};
return(
<Child someFn={someFn} currentValue = {currentDate }/>
);
}
function Child({ someFn, currentDate }) { ... };
// Wrap function in useCallback() to prevent rerendering the child component
const someFn = useCallback(() => {
...
}, [dependencies]);
export default React.memo(Child);
Prevent useEffect() from creating an infinite loop
If you invoke a function inside the useEffect()
hook,
useEffect()
expects the function to be
declared as a dependency. So each time the function changes,
useEffect()
runs. But running
useEffect()
causes the component to
recreate the function, which then causes
useEffect()
to run again, resulting in an
infinite loop. For example, the code below can cause an infinite
loop:
const getItemFromCart = skuId => {
const cartItems = Object.values(commerceItems);
return cartItems.find(cartItem => cartItem.catRefId === skuId);
}
useEffect(() => {
const selectedCartItem = getItemFromCart(skuId);
setSomeState(someNewState);
...
}, [getItemFromCart]);
To avoid an infinite loop, wrap functions that run inside
useEffect()
with
useCallback()
. Wrapping the function in
useCallback()
ensures the function is not
recreated, so useEffect()
is not triggered to run
again.
When to use useMemo()
If a function includes a complex calculation, you can avoid recomputing
it by using the useMemo()
hook. This hook returns a
memoized value that is not recomputed unless the dependencies
change.
In the following example, code that displays images is wrapped in
useMemo()
to prevent the images from being
rerendered whenever the state changes:
<div
className="ProductImageSlider__Wrapper"
style={{transform: `translateX(${translateValue}px)`}}
ref={slideWrapperEl}
onTouchStart={handleTouchStart}
onTouchMove={handleTouchMove}
onClick={() => setPortalRenderedCallback(!portalRendered)}
>
{useMemo(() => {
return images.map(image => <Slide key={image} image={image} />);
}, [images])}
</div>