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 ???
In my opinion, useCallback is not for performance. I cannot think of any reason that defining a function is really expensive.
Unlike useMemo, useCallback just memoize the function and does not actually execute it.
So when should we use it?
The main use case is to prevent re-running a function unnecessarily. Redefining a function is not problematic, but re-running it on every state update is buggy and often dangerous.
useCallback when the function needs to be inside dependency array of useEffectThere are two cases I can think of right now:
const [data, setData] = useState([]);
const [filter, setFilter] = useState({});
const fetchData = useCallback(async () => {
const response = await fetchApi(filter);
setData(response.data);
}, [filter]);
useEffect(() => {
fetchData();
}, [fetchData]);
(If the function is not async, we may use useEffect directly without using useCallback)
However, no need to wrap it with useCallback when it is only run by user interaction:
const [data, setData] = useState([]);
const [filter, setFilter] = useState({});
const fetchData = async () => {
const response = await fetchApi(filter);
setData(response.data);
};
return (
<button onClick={fetchData}>Fetch Data</button>
);
const onAwesomeLibarayLoaded = useCallback(() => {
doSomething(state1, state2);
}, [state1, state2]);
<AwesomeLibrary
onLoad={onAwesomeLibarayLoaded}
/>
Because AwesomeLibrary component might do something like example 1 with passed onLoad function:
const AwesomeLibarary = ({onLoad}) => {
useEffect(() => {
// do something
onLoad();
}, [onLoad]);
};
If you're sure it is not inside useEffect then it is OK even if you don't use useCallback.