Technologies:
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)
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 IlegboduTolerim
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>
);
}