Back

Technologies:

javascriptjavascript
reactjsreactjs
avatar
Tolerim
a month ago

Is it possible to utilize either useCallback exclusively, useMemo exclusively, or both?

While exploring the useCallback and useMemo hooks in React docs, I encountered some confusion. I wonder if I have to use both hooks simultaneously in the given example from the documentation. The starter code is shown below:
function ProductPage({ productId, referrer, theme }) {
  // ...
  return (
    <div className={theme}>
      <ShippingForm onSubmit={handleSubmit} />
    </div>
);
By toggling the theme prop, the app freezes momentarily. However, removing <ShippingForm /> from the JSX makes the app feel faster. The root cause of this issue stems from the fact that React re-renders all of its children recursively when a component re-renders by default. Therefore, to mitigate this problem, we can optimize the ShippingForm component by wrapping it in memo, which tells it to skip rendering when its props are the same as last render. Here is an example:
import { memo } from 'react';

const ShippingForm = memo(function ShippingForm({ onSubmit }) {
  // ...
});
However, this optimization won't work if the handleSubmit function is defined using an arrow function or a normal function using the function keyword because, in Javascript, every function() {} or () => {} creates a new function object, similar to how the {} object literal creates a new object. Therefore, the props passed to ShippingForm will always be different, and the memo optimization won't work in this case. Considering this, we can use the useCallback hook to tell React to cache the function between re-renders. Here's an example of how to use it:
function ProductPage({ productId, referrer, theme }) {
  const handleSubmit = useCallback((orderDetails) => {
    post('/product/' + productId + '/buy', {
      referrer,
      orderDetails,
    });
  }, [productId, referrer]); 

  return (
    <div className={theme}>
      <ShippingForm onSubmit={handleSubmit} />
    </div>
  );
}
Now, the question is whether to keep telling ShippingForm to skip re-rendering when its props are the same as on last render by wrapping it in memo. Is it still necessary? Thanks for your help.
Answers(2)
avatar
Vogi
a month ago
Verified Answer
Great question! The useCallback hook is used to memoize a function so that it doesn't get recreated on every render. This is useful when you pass a function as a prop to a child component because it ensures that the child component doesn't re-render unnecessarily. On the other hand, the useMemo hook is used to memoize a value so that it doesn't get recalculated on every render. This is useful when you have an expensive computation that you don't want to run on every render. In your case, since you're passing handleSubmit as a prop to ShippingForm, you should use useCallback to memoize it. However, since ShippingForm is a functional component and doesn't have any expensive computations, you don't need to use useMemo to memoize it. I hope this helps! Let me know if you have any more questions.

Sources

useCallback vs useMemo - MediumUse useCallback() and useMemo() efficiently - Oracle Help CenterReact useMemo vs. useCallback: A pragmatic guide - LogRocket BloguseCallback vs. useMemo and when to use them - Stack OverflowCan I use useCallback only, or useMemo only or both?React useCallback & useMemo use cases | Ben Ilegbodu
avatar
Tolerim
a month ago
Yes, it is still necessary to tell ShippingForm to skip re-rendering when its props are the same as on the last render. Even with the use of useCallback, if the parent component re-renders, it will still pass new props to the ShippingForm component, triggering a re-render. Therefore, wrapping ShippingForm in memo will still optimize its performance by preventing unnecessary re-renders. Here's the updated code with both useCallback and memo implemented:
function ProductPage({ productId, referrer, theme }) {
  const handleSubmit = useCallback((orderDetails) => {
    post('/product/' + productId + '/buy', {
      referrer,
      orderDetails,
    });
  }, [productId, referrer]);

  const MemoizedShippingForm = memo(ShippingForm);

  return (
    <div className={theme}>
      <MemoizedShippingForm onSubmit={handleSubmit} />
    </div>
  );
}
;