let's say we have the components like this
const Example = () => {
const [counter, setCounter] = useState(0);
const increment = () => setCounter(counter => counter + 1);
return (
<div>
<Button onClick={increment} />
<div>{counter}</div>
</div>
);
}
When I passed the onClick
handler as an arrow function, my eslint
throw a warning:
error JSX props should not use arrow functions react/jsx-no-bind
As I read from an answer from this post: https://stackoverflow.com/questions/36677733/why-shouldnt-jsx-props-use-arrow-functions-or-bind#:~:text=Why%20you%20shouldn't%20use,previous%20function%20is%20garbage%20collected.
The short answer is because arrow function is recreated every time, which will hurt the performance. One solution proposed from this post is to wrapped in a useCallback hook, with empty array. And when I change to this, the eslint warning really disappear.
const Example = () => {
const [counter, setCounter] = useState(0);
const increment = useCallback(() => setCounter(counter => counter + 1), []);
return (
<div>
<Button onClick={increment} />
<div>{counter}</div>
</div>
);
}
However, there is also another opinion saying that overusing useCallback will eventually slowdown the performance due to the overheads of useCallback. One example is here: https://kentcdodds.com/blog/usememo-and-usecallback
This is making me really confused? So for Functional Components, when dealing with inline function handler, should I just write the arrow function (ignore the eslint) or always wrap it in a useCallback ???
This is a common misconception. The arrow function is recreated every time either way (although with
useCallback
subsequent ones may be thrown away immediately). WhatuseCallback
does is make it possible for the child component you use the callback on to not re-render if it's memoized.Let's look at the misconception first. Consider the
useCallback
call:That's executed like this:
Evaluate the first argument,
() => setCounter(counter => counter + 1)
, creating a functionEvaluate the second argument,
[]
, creating an arrayCall
useCallback
with those two arguments, get back a functionCompare with what you have if you don't use
useCallback
:That's much simpler: Create the function. It doesn't then have to do #2 and #3 above.
Let's move on to what
useCallback
actually does that's useful. Let's look at where the callback is used:Now, suppose
Button
is memoized withReact.memo
or similar. Ifincrement
changes every time your component renders, thenButton
has to re-render every time your component changes; it can't be reused between renders. But ifincrement
is stable between renders (because you useduseCallback
with an empty array), the memoized result of callingButton
can be reused, it doesn't have to be called again.Here's an example:
Note that clicking the button in
ComponentA
always callsButton
again, but clicking the button inComponentB
doesn't.When do you want to do that? That's largely up to you, but it probably makes sense when your component's state will change frequently in ways that don't affect the contents of
increment
and thus don't affectButton
and ifButton
has to do significant work when rendered.Button
probably doesn't, but other child components may.For instance, the
useCallback
in my previous example is probably pointless if you usecount
as the text of the button, since that meansButton
has to re-render regardless:Also note that
useCallback
isn't free, it impacts the code in the callback. Look at the code in the callbacks inComponentA
andComponentB
in the examples.ComponentA
(which doesn't useuseCallback
) can use the value ofcount
that it closes over (within limits!),() => setCount(count + 1)
. But the one inComponentB
always has to use the callback form of the setter,() => setCount(count => count + 1)
. That's because if you keep using the firstincrement
you create, thecount
it closes over will be stale — you'd see the count go to 1, but never further.A final note: If you're re-rendering a component so often that creating and throwing away the various functions you're passing to
useCallback
oruseMemo
may be causing too much memory churn (a rare situation), you can avoid it by using a ref. Let's look at updatingComponentB
to using a ref instead ofuseCallback
:That only creates the
increment
function once (in that example, since we don't have any dependencies), it doesn't create and throw away functions like usinguseCallback
does. It works because the initial value of the ref isnull
, and then the first time the component function is called, we see that it'snull
, create the function, and put it on the ref. Soincrement
is only created once.That example does recreate the function we pass
setCount
every timeincrement
is called. It's possible to avoid that, too:That's really going to 11 in terms of avoiding unnecessary function creation. :-)
It's a rare component that needs even that first level of optimization, much less the second level; but when/if you do, that's how you do it.