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>