I was reading React Docs about useCallback
and memo
and got a little confused if I had to use both at the same time in the example below from their docs.
Starter code:
function ProductPage({ productId, referrer, theme }) {
// ...
return (
<div className={theme}>
<ShippingForm onSubmit={handleSubmit} />
</div>
);
From the code above, toggling the theme
prop freezes the app for a moment, but if you remove <ShippingForm />
from the JSX, it feels fast. Therefore, we’ll have to optimize the ShippingForm
component because by default, when a component re-renders, React re-renders all of its children recursively. So, to solve the problem we can tell ShippingForm
to skip re-rendering when its props are the same as on last render by wrapping it in memo
as below:
import { memo } from 'react';
const ShippingForm = memo(function ShippingForm({ onSubmit }) {
// ...
});
However, if the handleSubmit
function is an arrow function or a regular function using the function keyword as below, the memo
optimization won’t work because in JavaScript, a
function () {}
or () => {}
always creates a different function, similar to how the {}
object literal always creates a new object. Hence, the ShippingForm
props will never be the same, and the memo
optimization won’t work.
The code below will not work
function ProductPage({ productId, referrer, theme }) {
// Every time the theme changes, this will be a different function...
function handleSubmit(orderDetails) {
post('/product/' + productId + '/buy', {
referrer,
orderDetails,
});
}
return (
<div className={theme}>
{/* ... so ShippingForm's props will never be the same, and it will re-render every time */}
<ShippingForm onSubmit={handleSubmit} />
</div>
);
}
And this also will not work
function ProductPage({ productId, referrer, theme }) {
// Every time the theme changes, this will be a different function...
const handleSubmit = (orderDetails)=> {
post('/product/' + productId + '/buy', {
referrer,
orderDetails,
});
}
return (
<div className={theme}>
{/* ... so ShippingForm's props will never be the same, and it will re-render every time */}
<ShippingForm onSubmit={handleSubmit} />
</div>
);
}
So, to solve the issue we’ll have to use the useCallback
hook as below.
Now my question is since we’re using the useCallback
hook do we still keep telling ShippingForm
to skip re-rendering when its props are the same as on last render by wrapping it in memo
? Or it’s unnecessary?
Thanks for helping.
function ProductPage({ productId, referrer, theme }) {
// Tell React to cache your function between re-renders...
const handleSubmit = useCallback((orderDetails) => {
post('/product/' + productId + '/buy', {
referrer,
orderDetails,
});
}, [productId, referrer]); // ...so as long as these dependencies don't change...
return (
<div className={theme}>
{/* ...ShippingForm will receive the same props and can skip re-rendering */}
<ShippingForm onSubmit={handleSubmit} />
</div>
);
}
2
Answers
Yes, you should use both.
P.S.
You have a little mistake in your question: useMemo is a hook for memoizing values inside component (like useCallback but it saves result of function execution). React.memo is almost same stuff but for Components itself. You are asking about last thing.
All the confusion is coming from the fact you are assuming a component renders when receiving different props. Actually, a component only renders when its internal state changes or when its parent renders. This is why, this
Child
component with no props, gets called again when its parent renders:When this behavior is problematic, because
Child
does lots of computing, for example, you could tell React with the help ofmemo
to render it only when it receives new props:memo
compares the previous props with the new one (while renderingParent
). If it’s the same, it skips renderingChild
. This would work whether you are not passing any props like the above or primitive ones. However, if you pass an object or a function defined in the body ofParent
, they get passed down with different memory references, hence we useuseCallback
to memoize functions, anduseMemo
to memoize objects:So you need both or one of them depending on your use case.