Are you using useCallback properly 🤔
source link: https://dev.to/markoarsenal/are-you-using-usecallback-properly-5g2c
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
Posted on Mar 20
Are you using useCallback properly 🤔
I didn't up until recently.
On the project my team is working on we were use useCallback
for every function prop passed to the child components.
This approach doesn't give you benefits as you may expect.
Our code looked like this (not literally 😀)
const ParentComponent = () => {
...
const onClick = useCallback(() => console.log('click'), [])
return <ChildComponent onClick={onClick} />
}
const ChildComponent = ({ onClick }) => {
return <button onClick={onClick}>Click me</button>
}
Enter fullscreen mode
Exit fullscreen mode
Approach without useCallback
const ParentComponent = () => {
...
return <ChildComponent onClick={() => console.log('click')} />
}
Enter fullscreen mode
Exit fullscreen mode
The benefits of the first approach compared to the second one are minimal and in some cases considering the cost of useCallback
the second approach is faster.
The thing is creating and destroying functions on each rerender is not an expensive operation as you may think and replacing that with useCallback
doesn't bring much benefits.
Another reason why we always used the useCallback
hook is to prevent the child component rerender if its props didn't change but this was wrong because whenever the parent component rerenders the child component will rerender as well nevertheless the child props are changed or not.
React.memo
If you want to rerender the child component only when its props or state changed you want to use React.memo.
You can achieve the same with PureComponent or shouldComponentUpdate if you are working with class components instead of functional.
If we wrap ChildComponent from our first example with React.memo
const ChildComponent = React.memo(({ onClick }) => {
return <button onClick={onClick}>Click me</button>
})
Enter fullscreen mode
Exit fullscreen mode
when the ParentComponent rerenders and props of the ChildComponent don't change, the ChildComponent will not rerender.
This gives us a good insight when we should use useCallback
hook.useCallback
should be used in combination with the React.memo
.
I will not say that should be always the case, you can use useCallback without React.memo if you find it useful but in most cases, those two should be the pair. ❤
When to use React.memo
There are no clear instructions on when to do it, someone thinks you should use it always and I'm for the approach "measure performance of your component and optimize it with React.memo
if needed".
The components which you can wrap with React.memo
by default are those with a lot of children like tables or lists.
Now we will take a look at an example.
You can clone it and try it by yourself from here https://gitlab.com/markoarsenal/use-callback.
It looks like this (very creative 😀)
We have a long list of comments (a good candidate for React.memo) and we have the counter button on the top whose main purpose is to trigger the rerender.
The code looks like this
const Home = () => {
const [counter, setCounter] = useState(0);
const onClick = useCallback(() => console.log("click"), []);
return (
<Profiler
id="Home page"
onRender={(compName, mode, actualTime, baseTime) =>
console.log(compName, mode, actualTime, baseTime)
}
>
<main className="max-w-5xl p-8 m-auto">
<div className="flex justify-center mb-8">
<button
onClick={() => setCounter(counter + 1)}
className="px-3 py-1 border border-gray-500"
>
Update {counter}
</button>
</div>
<Comments comments={comments} onClick={onClick} />
</main>
</Profiler>
);
};
Enter fullscreen mode
Exit fullscreen mode
You can notice Profiler
component as a root component, it's this one https://reactjs.org/docs/profiler.html.
We are using it to measure rendering times.
You can notice onRender
callback, we are logging a couple of things inside but the most important are actualTime and baseTime. The actualTime is the time needed for component rerender and baseTime is the time to rerender component without any optimizations. So if you don't have any optimizations within your component actualTime and baseTime should be equal.
Comments
component looks like this (notice that is wrapped with React.memo)
const Comments = ({ comments, onClick }: CommentsProps) => {
return (
<section>
{comments.map((comment) => {
return (
<Comment
{...comment}
className="mb-4"
onClick={onClick}
key={comment.id}
/>
);
})}
</section>
);
};
export default memo(Comments);
Enter fullscreen mode
Exit fullscreen mode
Now I will run our example with 500 comments in Chrome, hit the "Update" button a few times to cause rerender and post results here.
So, on every rerender we are saving around 30ms which is considerable.
Let's try one more thing, instead of the list of the comments to render one, memoized comment and see what measurements are.
{/* <Comments comments={comments} onClick={onClick} /> */}
<Comment {...comments[0]} onClick={onClick} />
Enter fullscreen mode
Exit fullscreen mode
Still, we have time savings but they are neglecting which means that React does not have trouble rerendering those small and simple components, and memoizing those doesn't have much sense.
On the other hand memoizing component that contains a lot of children is something you can benefit from.
Hope you enjoyed reading the article and that now you have a better overview of useCallback
and React.memo
and when to use them.
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK