New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Global event handlers on document.body (or other containing element) run BEFORE react event handlers #7094

Closed
vitalif opened this Issue Jun 21, 2016 · 9 comments

Comments

Projects
None yet
9 participants
@vitalif
Copy link

vitalif commented Jun 21, 2016

I've discovered that if I add global event handler with document.body.addEventListener('click', ...) in a component I cannot stop it with ev.stopPropagation() inside React event handler, because global native event handler runs BEFORE the react one. I'm able to stop it only if I set it with window.addEventListener...
I think it's a bad behaviour. Is React setting all synthentic events on window instead of specific DOM elements?
Can it be fixed?

@gaearon

This comment has been minimized.

Copy link
Member

gaearon commented Jun 28, 2016

I think it's a bad behaviour. Is React setting all synthentic events on window instead of specific DOM elements?
Can it be fixed?

This is actually beneficial—event delegation is preferable to setting event handlers on every DOM element. IIRC we sometimes add event handlers on specific DOM nodes when absolutely necessary but most of the times we try to reuse the same top-level handler. This is described in the docs:

Event delegation: React doesn't actually attach event handlers to the nodes themselves. When React starts up, it starts listening for all events at the top level using a single event listener. When a component is mounted or unmounted, the event handlers are simply added or removed from an internal mapping. When an event occurs, React knows how to dispatch it using this mapping. When there are no event handlers left in the mapping, React's event handlers are simple no-ops. To learn more about why this is fast, see David Walsh's excellent blog post.

I would say that generally how React implements events is an implementation detail, and it might change. I wouldn’t suggest trying to prevent a native event handler from a synthetic event handler, or vice versa. If you use events in React, please consider treating it as an encapsulated system.

As an escape hatch, you can always sidestep React event system and use refs with DOM APIs:

class MyComponent extends React.Component {
  componentDidMount() {
    this.node.addEventListener(...)
  }

  componentWillUnmount() {
    this.node.removeEventListener(...)
  }

  render() {
    return <button ref={node => this.node = node}>Hi</button>
  }
}

This gives you access to raw DOM APIs and lets you attach handlers to the DOM elements you want. But this comes with a performance penalty so I wouldn’t suggest doing it too often.

I hope this helps!

@gaearon gaearon closed this Jun 28, 2016

@vitalif

This comment has been minimized.

Copy link
Author

vitalif commented Jun 28, 2016

Thank you for the detailed explanation :-)

@alexmnv

This comment has been minimized.

Copy link

alexmnv commented Aug 31, 2017

I wouldn’t suggest trying to prevent a native event handler from a synthetic event handler, or vice versa.

Is it possible to attach a synthetic event handler to document.body (so that it was cancelable from React's event handler)?

@AnderssonChristian

This comment has been minimized.

Copy link

AnderssonChristian commented Oct 19, 2017

@gaearon Question about the snippet you posted above: Is it really necessary to manually remove the event listener? If the listener is attached to the Node directly, won't it be removed automatically along with the Node itself upon Unmount?

I know this is very anti-pattern, but this got me curious... wouldn't the below snippet suffice?

class MyComponent extends React.Component {
  componentDidMount() {
    this.node.addEventListener(...)
  }

  render() {
    return <button ref={node => this.node = node}>Hi</button>
  }
}
@samrat41

This comment has been minimized.

Copy link

samrat41 commented Dec 1, 2017

hi @gaearon , I could not find the event delegation explanation which you referred in above post in latest react documentation. docs. Any change in that approach? If not, could you direct me to the link where I can find that info.

@qtuan

This comment has been minimized.

Copy link

qtuan commented Dec 19, 2017

Second the question: Is it possible to attach a synthetic event handler to document.body?
Or is it possible to create a synthetic event from a native event?

I want to make use of its "cross-browser wrapper around the browser’s native event" benefit. My use case is related to keyboard shortcuts handling. I originally use onKeyDown on specific containers, but need to move up higher the DOM tree to cover more cases, e.g when keys are pressed inside some help text popovers, which are actually rendered outside the App's root.

@jacobp100

This comment has been minimized.

Copy link

jacobp100 commented Jan 10, 2018

I use global events a lot, but there is always hacks required to get the two event systems to sync up. As a suggestion of API, what about,

const Popover = props => {
  let container

  const handleClick = e => {
    if (!container.contains(e.target)) {
      props.onClose()
    }
  }

  const setRef = r => {
    container = r
  }

  return (
    <div ref={setRef} onGlobalClick={handleClick}>
      {props.children}
    </div>
  )
}

I had an experiment with attaching events to a root react node, but you have to keep the root node focused. You can see it here, but would definitely like to try something less hacky.

@wenfangcn

This comment has been minimized.

Copy link

wenfangcn commented Mar 8, 2018

"hi @gaearon , I could not find the event delegation explanation which you referred in above post in latest react documentation. docs. Any change in that approach? If not, could you direct me to the link where I can find that info."+1

@dziamid

This comment has been minimized.

Copy link

dziamid commented Apr 6, 2018

When React starts up, it starts listening for all events at the top level using a single event listener.

Any specific reason why the root listener cannot sit on app's root node, rather than the document? That would IMO make react more self-contained and easier embeddable into contexts that already have their document listeners setup by the time react app initializes.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment