Logo

dev-resources.site

for different kinds of informations.

Why isn't event.stopPropagation() working?!

Published at
7/9/2020
Categories
react
webdev
javascript
canopy
Author
dvnrsn
Categories
4 categories in total
react
open
webdev
open
javascript
open
canopy
open
Author
6 person written this
dvnrsn
open
Why isn't event.stopPropagation() working?!

Today I spent a number of hours with a coworker trying to figure out why a dropdown component was unmounting on certain clicks.

There is a cool date-range picker built by another coworker that allows the user to select two different days like so:

Alt Text

Our QA had discovered a bug where clicking a day in June when looking at May caused the entire thing to close.

Alt Text

What? Why is that happening?

We had solved a similar problem a few months ago so we spent a minute tracking slack and git history to try and see what we had changed. Eventually my coworker discovered the problem: there was an event that was happening on click of document in which we were checking to see if the event's target was a child node and closing if not the case.

componentDidMount() {
  document.addEventListener('click', this.handlePageClick);
}

handlePageClick = e => {
  if (!this.myDiv.contains(e.target)) {
    this.hideChild();
  }
};
Enter fullscreen mode Exit fullscreen mode

But myDiv does contain the target..? Well yes, in every case except the May example. You'll notice in the May example there is an extra row. That row disappears as soon as you click a day in June because our component is unmounting those nodes before the document click event listener is running. Watch what happens when I disable the click event.

Alt Text

If the document click event listener was active, it'd try and find the DOM node that is no longer there on that 6th row.

There are a few ways we could have approached this.

  • have a ghost 6th row that is always present but only sometimes visible
  • delay the removing of the row until the document click event fire finished (callback, timeout?)
  • add a property to the event.nativeEvent object that we could check for from the document event listener to conditionally unmount

We eventually decided to pursue event bubbling. It didn't make sense to have any click events bubbling out of the popup, so why not kill them? Problem is: they already were killed. We saw this all over the code:

<div onClick={e => e.stopPropagation()}>
Enter fullscreen mode Exit fullscreen mode

So why then was it still firing? Why isn't event.stopPropagation() working?!

I came to the conclusion that the document event listener was indifferent to any children clicks after drafting a codesandbox that showed the document event listener firing regardless of stopPropagation.

But then my coworker tweeked w3school's sandbox to show me I was wrong. So I was then scratching my head.

Then it hit me. React's SyntheticEvent. I remembered that React uses a single event listener on the document. If we're adding others and trying to stop propagation, that's probably meaningless to other document event listeners because they're on the same node.

I learned that the workaround is to use window.addEventListener() instead which solved our problem instantly. Too many hours to learn a hard lesson: review and master the React docs.

componentDidMount() {
  // document.addEventListener('click', this.handlePageClick);
  window.addEventListener('click', this.handlePageClick);
}
Enter fullscreen mode Exit fullscreen mode

Featured ones: