useHover hooked a fool. The fool was me
Tags: javascript, react, hooks
Published: 2020-02-23 19:37:05

TLDR: useHover may trigger unnecessary re-renders: [demo](https://codesandbox.io/s/usehover-sl3br), [solution](https://codesandbox.io/s/usehover-v2-jxfdp)

This weekend I found The Guardian's [blog post](https://www.theguardian.com/info/2019/dec/08/migrating-the-guardian-website-to-react) about their website migration to React.  At the end of the post, they mention DCR. It's a frontend rendering framework for theguardian.com and it's available on [Github](https://github.com/guardian/dotcom-rendering). I was so interested in how it is designed inside so I've started my research.

Performance is one of things that I was interested in. So I've tried to find any usages of `React.memo`, `PureComponent` or `shouldComponentUpdate`. And I was so surprised when I found nothing.

I've gone back to their site and started profiling it. And I didn't found any unnecessary re-renders because it just renders article. The data never changes, the page doesn't have any tricky handlers. So any optimizations will be just extra costs here. But then I've found this.

![Most viewed articles component](https://dev-to-uploads.s3.amazonaws.com/i/l9wdk57j1tzwvg6datjb.jpg)

It's a simple component to render most viewed articles aside. And it re-renders on a hover event because its content styled programmatically. So it looks logical: you hover X and it re-renders because now it's hovered. After you hover Y and X with Y re-render because the state of both components changed. But what if I show you this?

![Mouse movement inside the component triggers re-renders](https://dev-to-uploads.s3.amazonaws.com/i/vmiqfsy5fsy9s07pe7y1.gif)

*Blue and orange boxes is not a part of the component. React profiler shows it when a component re-renders*

Mouse movement inside the component still trigger re-renders, but I had no idea why. I've wrapped internal components with `React.memo`, callbacks with `useCallback` and other things that usually help. But this component was still re-render. React profiler showed that props still change. Then I thought maybe [`useHover`](https://github.com/guardian/dotcom-rendering/blob/177833ee516ed7d6cf80075a30faf66123962d07/src/web/lib/useHover.tsx#L1) has some issues. But it didn't.

So I've written a plain html+css+js [demo](https://jsfiddle.net/mbelsky/831t670y/) and shared with some friends to whine and complain why mouse movement inside a hovered element triggers `mouseout` and `mouseover` events. And they helped me. I have forgotten about a core mechanic of javascript events. The mechanic is [event bubbling and capture](https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Building_blocks/Events#Event_bubbling_and_capture). The demo extended [with extra logging](https://jsfiddle.net/mbelsky/831t670y/13/) `currentTarget` shows it.

Unfortunately `e.stopPropagation` does not solve current issue, so I've implemented a throttling mechanism for that hook with `setTimeout`.

{% codesandbox jxfdp %}

*The original version of the demo is available [here](https://codesandbox.io/s/usehover-sl3br)*

As before, mouse event handlers trigger immediately (you may track it by console logs), but `setState` (prefixed with `Deferred`) will be called on the next event loop tick only. So if we have two or more serial events, the hook won't call `setState` on every event, it will work only once with the latest `true/false` value.

It was a good reminder to don't forget javascript basics because React is just a library that builded on them.