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?
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鈥攅vent 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鈥檛 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 ref
s 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鈥檛 suggest doing it too often.
I hope this helps!
Thank you for the detailed explanation :-)
I wouldn鈥檛 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)?
@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>
}
}
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.
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鈥檚 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.
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.
"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
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.
We're attaching events to roots in React 17.
We're attaching events to roots in React 17.
This one is much needed change to React.
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
@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.
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.
17.x releases might have some new features though. Just nothing huge.
Thanks for clarifying @gaearon
Most helpful comment
Is it possible to attach a synthetic event handler to
document.body
(so that it was cancelable from React's event handler)?