Declarative API for installing global DOM event handlers
See original GitHub issue#284 reminded me that one thing I’ve sometimes wanted is to install a handler on window for keypress
(for keyboard shortcuts) or scroll
. Right now I can just do window.addEventListener
in componentDidMount
but since React is listening already, it would be nice if there were some way for me to intercept those events. (In addition, receiving normalized synthetic events is generally more useful.)
Issue Analytics
- State:
- Created 10 years ago
- Reactions:47
- Comments:59 (25 by maintainers)
Top Results From Across the Web
Declarative APIs in an Imperative World - InfoQ
We have this command React. Component which wraps the command registry API and allows you to add new commands.
Read more >Events - Ractive.js
With Ractive.js, events are declarative instead, and you declare an event handler like this: <button on-click="@global.alert( 'Activating!' )">Activate!
Read more >chrome.events - Chrome Developers
Example APIs using Events: alarms, i18n, identity, runtime. Most chrome APIs do. # Declarative Event Handlers. The declarative event handlers provide a means...
Read more >Events - Polymer Project
Annotated event listener setup ... To add event listeners to local DOM children, use on- event annotations in your template. This often eliminates...
Read more >JavaScript, Events, DOM APIs - A-Frame
Components encapsulate all of our code to be reusable, declarative, and shareable. Though if we're just poking around at runtime, we can use...
Read more >
Top Related Medium Post
No results found
Top Related StackOverflow Question
Troubleshoot Live Code
Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free
Top Related Reddit Thread
No results found
Top Related Hackernoon Post
No results found
Top Related Tweet
No results found
Top Related Dev.to Post
No results found
Top Related Hashnode Post
No results found
Right now, the only way to respond to “outside world” events is to leave the React’s event system and add a native DOM listener. This is bad, since it will require more mental overhead when you have to work with this (you need to think about your event listener receiving a native event, or a react synthetic event). It will also simply not be possible for computed
SyntheticEvent
s (e.g.onChange
).It also makes it very hard for react events handlers to interrupt the DOM handlers (This issue is mentioned above). Consider the following example, where it’s not intuitive why the React listener can not stop propagation to the
document
. (Spoiler: React also listens ondocument
, that’s why you’d have to useSyntheticEvent#nativeEvent.stopImmediatePropagation()
:An example for when you want to deal with outside events is a simple drawing tool, that must listen on
keyup
to stop the drawing process - Otherwise, the UI would feel broken. Right now, without leaving React’s event system, I could only listen on mosueup event at my own root component and pass this callback to the child that’s responsible for the drawing but I can’t listen on thosemouseup
events outside my component or even outside the browser (although React’s event hub would capture those by listening ondocument
).There are a lot of solution ideas - most of them are tied to DOM specific features like
document
orwindow
. I don’t think that this is a way that React would like to go - that’s why I think we should make the approach more abstract.I can think of a new public API, something like an
EventRoot
. It should behave like a regular DOM Node, so that you canaddEventListener()
andremoveEventListener()
, but its callbacks will receive theSyntheticEvent
. TheEventRoot
is created for every root react component (whereinstance._hostParent === null
. It should be accessible inside components by calling something likethis.eventRoot.addEventListener()
so that it’s trivial to migrate for people that are currently relying on DOM event systems (e.g.document.addEventListener()
). (Edit: This API could be made declarative as well e.g.onRootMouseDownCapture
.)The EventRoot get involved when triggering a two-phase dispatch. It respects the
capture
andbubble
order as well asstopPropagation()
. Everything you’d expect when listening ondocument
. But stopping propagation will be isolated to the specific React instance => Two react trees that listen on theEventRoot
can’t interfere.This API should help to further abstract the fact that React will listen on
document
so that people don’t need to rely on this fact anymore.For the above example, you’d only have to replace
document
with the new event root. ThestopPropagation()
can now correctly be applied.I’d love to hear what you think about this and how I could help shape the future of React’s event system. 😊
Actually, none of the solutions mentioned above were sufficient for me, and I thought I had a pretty general case. I needed some simple global hotkeys. Binding them natively on document in
component-did-mount
worked of course, like other solutions using mousetrap or keymaster. The problem is, like @philipp-spiess illustrated, any other input field receiving synthetic keydowns and on whichstopPropagation
have been called are still fired up to the native document keydown listener. This is especially annoying when you have hotkeys that aren’t prefixed (meta, alt, ctrl) like ‘q’ or ‘v’ => anytime a user inputs that key in an input field a global hot key would be called.For anyone having the same problem, here’s a neat little solution/trick I came up with that might help you and has not been offered in this thread or anywhere for that matter: Bind it twice - once on document, and once at the top of your react tree. The document handler checks if
e.target == document.body
(or whatever fits your needs), if so it fires. All the other ones are caught by the one bound to the react root. This way:stopPropagation
to prevent the event from bubbling to the top of the react tree, or not and the hot key fires.This can of course be applied to any other events, like clicks etc…
A very simple mockup of the idea
Working demo on Codepen