gaearon/react-hot-loader

document click listener function executed twice: before and AFTER component rendered.

carlesnunez opened this issue · 4 comments

Description

Hello, I have a problem related with @hot-loader/react-dom I have a situation in which when clicking a button a popover menu gets opened. When the popover is opened I have a componentDidMount method that executes the next code:

componentDidMount = () => {
document.addEventListener('click', this.handleOutsideClick)
}

The problem is that, this listener GET EXECUTED with the even that has been triggered by clicking the button that opens this component 🤯

Expected behavior

On clicking the "open popover" button the componentDidMount adds an event listener but the callback is not called.

Actual behavior

The current behavior is that, on clicking on the "open popover button" the popover component get rendered, the componentdidmount code is executed BUT the inmediatelly there's a click dispatched that casually is the same click that I did on the button. Is like we were recycling somehow the button click defeering its listener execution after component is mounted.

Can you help?

Ok I recently discovered something. It seems that adding e.stopPropagation to the button handler avoids the native event from being executed which lead me to the point in which it seem that the bridge between synthetic event bubbling to native events is being blocked by the component rendering execution.

Adding e.stopPropagation solves the problem but I don't like to have this side effects.

Any clue on whats going on? @gaearon 🙏

Thanks in advance.

Neither @gaearon nor @theKashey are actively looking at React-Hot-Loader anymore. It was deprecated a year ago and nowadays you are expected to use "natively supported" Fast Refresh, not "hacky" RHL

But there are some clues. I am not sure that your problem is related to RHL, but there are few issues with "arrow methods" as well as with life cycle ones, for example you might not be able to remove this handler, as this.handleOutsideClick can point to another "version" of it.

As well as it can be added before "control" will be returned to the browser (pretty sure it's the case), and this has nothing with RHL.


The fix is simple - use useEffect, as it will be executed "later". Note that componentDidMount is equal to useLayoutEffect, and you can run into the problem I've described above.

If you are not able to convert this one to a functional component or defer this logic to another component - try wrapping addEventListener with some cancelable setTimeout.

In any case, converting class-based one to the functional-based one will:

  • fix the problem (useEffect)
  • fix the problem (connect addEventListener and removeEventListener, preventing the first issue I've described above)
  • fix the problem (FastRefresh)

Thank you for taking your time on replying me. I'll definetivelly try it!