Skip to content
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 · 16 comments

Comments

@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
Copy link
Collaborator

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 as completed Jun 28, 2016
@vitalif
Copy link
Author

vitalif commented Jun 28, 2016

Thank you for the detailed explanation :-)

@alexmnv
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
Copy link

@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
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
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
Copy link

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.

@wenfangdu
Copy link

"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
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.

@gaearon
Copy link
Collaborator

gaearon commented Aug 10, 2020

We're attaching events to roots in React 17.

@KA-32
Copy link

KA-32 commented Aug 11, 2020

We're attaching events to roots in React 17.

This one is much needed change to React.

@gaearon
Copy link
Collaborator

gaearon commented Aug 11, 2020

If you want to try it, we released 17 RC yesterday with this change:
https://reactjs.org/blog/2020/08/10/react-v17-rc.html

@AnderssonChristian
Copy link

@gaearon after reading the blog, it appears that version 17 and any subsequent 17.(x) will not have any new features. As the blog points out, 17 will be a stepping stone. Does this mean that we can expect version 18 to follow 17 in the not-too-distant future? Or will 17 be the latest major for a considerable time? In a way, this almost "feels" like a 16.(x) version.

@gaearon
Copy link
Collaborator

gaearon commented Aug 11, 2020

I'd expect 18 to ship within a year or less but my estimates haven't been very accurate historically.
Definitely don't want another two year gap there though.

@gaearon
Copy link
Collaborator

gaearon commented Aug 11, 2020

17.x releases might have some new features though. Just nothing huge.

@AnderssonChristian
Copy link

Thanks for clarifying @gaearon

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

10 participants