The short answer is because arrow function is recreated every time, which will hurt the performance.
This is a common misconception. The arrow function is recreated every time either way (although with useCallback
subsequent ones may be thrown away immediately). What useCallback
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:
const increment = useCallback(() => setCounter(counter => counter + 1), []);
That's executed like this:
Evaluate the first argument, () => setCounter(counter => counter + 1)
, creating a function
Evaluate the second argument, []
, creating an array
Call useCallback
with those two arguments, get back a function
Compare with what you have if you don't use useCallback
:
const increment = () => setCounter(counter => counter + 1);
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:
<Button onClick={increment} />
Now, suppose Button
is memoized with React.memo
or similar. If increment
changes every time your component renders, then Button
has to re-render every time your component changes; it can't be reused between renders. But if increment
is stable between renders (because you used useCallback
with an empty array), the memoized result of calling Button
can be reused, it doesn't have to be called again.
Here's an example:
const { useState, useCallback } = React;
const Button = React.memo(function Button({onClick, children}) {
console.log("Button called");
return <button onClick={onClick}>{children}</button>;
});
function ComponentA() {
console.log("ComponentA called");
const [count, setCount] = useState(0);
// Note: Safe to use the closed-over `count` here if `count `updates are
// triggered by clicks or similar events that definitely render, since
// the `count` that `increment` closes over won't be stale.
const increment = () => setCount(count + 1);
return (
<div>
{count}
<Button onClick={increment}>+</Button>
</div>
);
}
function ComponentB() {
console.log("ComponentB called");
const [count, setCount] = useState(0);
// Note: Can't use `count` in `increment`, need the callback form because
// the `count` the first `increment` closes over *will* be slate after
// the next render
const increment = useCallback(
() => setCount(count => count + 1),
[]
);
return (
<div>
{count}
<Button onClick={increment}>+</Button>
</div>
);
}
ReactDOM.render(
<div>
A:
<ComponentA />
B:
<ComponentB />
</div>,
document.getElementById("root")
);
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.0/umd/react-dom.production.min.js"></script>
Note that clicking the button in ComponentA
always calls Button
again, but clicking the button in ComponentB
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 affect Button
and if Button
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 use count
as the text of the button, since that means Button
has to re-render regardless:
const { useState, useCallback } = React;
const Button = React.memo(function Button({onClick, children}) {
console.log("Button called");
return <button onClick={onClick}>{children}</button>;
});
function ComponentA() {
console.log("ComponentA called");
const [count, setCount] = useState(0);
// Note: Safe to use the closed-over `count` here if `count `updates are
// triggered by clicks or similar events that definitely render, since
// the `count` that `increment` closes over won't be stale.
const increment = () => setCount(count + 1);
return (
<div>
<Button onClick={increment}>{count}</Button>
</div>
);
}
function ComponentB() {
console.log("ComponentB called");
const [count, setCount] = useState(0);
// Note: Can't use `count` in `increment`, need the callback form because
// the `count` the first `increment` closes over *will* be slate after
// the next render
const increment = useCallback(
() => setCount(count => count + 1),
[]
);
return (
<div>
<Button onClick={increment}>{count}</Button>
</div>
);
}
ReactDOM.render(
<div>
A:
<ComponentA />
B:
<ComponentB />
</div>,
document.getElementById("root")
);
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.0/umd/react-dom.production.min.js"></script>
Also note that useCallback
isn't free, it impacts the code in the callback. Look at the code in the callbacks in ComponentA
and ComponentB
in the examples. ComponentA
(which doesn't use useCallback
) can use the value of count
that it closes over (within limits!), () => setCount(count + 1)
. But the one in ComponentB
always has to use the callback form of the setter, () => setCount(count => count + 1)
. That's because if you keep using the first increment
you create, the count
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
or useMemo
may be causing too much memory churn (a rare situation), you can avoid it by using a ref. Let's look at updating ComponentB
to using a ref instead of useCallback
:
const incrementRef = useRef(null);
if (!incrementRef.current /* || yourDependenciesForItChange*/) {
// Note: Can't use `count` in `increment`, need the callback form because
// the `count` the first `increment` closes over *will* be slate after
// the next render
incrementRef.current = () => setCount(count => count + 1);
}
const increment = incrementRef.current;
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 using useCallback
does. It works because the initial value of the ref is null
, and then the first time the component function is called, we see that it's null
, create the function, and put it on the ref. So increment
is only created once.
That example does recreate the function we pass setCount
every time increment
is called. It's possible to avoid that, too:
const incrementRef = useRef(null);
if (!incrementRef.current) {
// Note: Can't use `count` in `increment`, need the callback form because
// the `count` the first `increment` closes over *will* be slate after
// the next render
const incrementCallback = count => count + 1;
incrementRef.current = () => setCount(incrementCallback);
}
const increment = incrementRef.current;
const { useState, useRef } = React;
const Button = React.memo(function Button({onClick, children}) {
console.log("Button called");
return <button onClick={onClick}>{children}</button>;
});
function ComponentA() {
console.log("ComponentA called");
const [count, setCount] = useState(0);
// Note: Safe to use the closed-over `count` here if `count `updates are
// triggered by clicks or similar events that definitely render, since
// the `count` that `increment` closes over won't be stale.
const increment = () => setCount(count + 1);
return (
<div>
{count}
<Button onClick={increment}>+</Button>
</div>
);
}
function ComponentB() {
console.log("ComponentB called");
const [count, setCount] = useState(0);
const incrementRef = useRef(null);
if (!incrementRef.current) {
// Note: Can't use `count` in `increment`, need the callback form because
// the `count` the first `increment` closes over *will* be slate after
// the next render
const incrementCallback = count => count + 1;
incrementRef.current = () => setCount(incrementCallback);
}
const increment = incrementRef.current;
return (
<div>
{count}
<Button onClick={increment}>+</Button>
</div>
);
}
ReactDOM.render(
<div>
A:
<ComponentA />
B:
<ComponentB />
</div>,
document.getElementById("root")
);
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.0/umd/react-dom.production.min.js"></script>
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.
react/jsx-no-bind
, in my opinion, is most likely for React Component Classes, not for functional components. I may be wrong about that though. IMO it's giving you an error because it can't tell the difference between stateful and functional components – Voussoirreact/jsx-no-bind
from the eslint settings and just remember to use an autobinder in all my stateful classes, like this one. It's easy for me to remember because my IDE allows me to have templates, so whenever I'm creating a new React class I just use my template which includes the autobinder. – Voussoir