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.)Yes, I've wanted this for resize
as well. We talked once about adding maybe a onWindowResize
event that fires on every component, but how would it bubble?
+1 on that, just encountered a case for that
We talked once about adding maybe a onWindowResize event that fires on every component, but how would it bubble?
If it fires on every component does it makes sense for it to bubble?
I wanted this for mousemove and mouseup as well. :) We're thinking about a larger declarative event system that could do conflict resolution but we should probably get this in the mean time. Not sure about the API though.
For mousemove and mouseup, I think @jordwalke was suggesting using ResponderEventPlugin…
I'll also add that a way to bind to onbeforeunload declaratively could be helpful.
Also will be cool to have context keyDown. Like context hokeys for keyboard driven apps.
@Aetet Sadly though, all of them operate on the assumption of US/Western keyboard layouts, unless you're willing to avoid support for ctrl
and alt
. Also, you could easily make this as a Mixin yourself.
@spicyj @petehunt As for this specific PR, what about simply exposing it as React.addEventListener(node, event, callback)
(could be useful for when leaving the confines of React, like innerHTML
) or as a mixin ReactEventsMixin
+ listenToEvents: [{event: callback}]
which could then take care of the cleaning up itself.
Depending on the use-cases, I guess you could even do ReactWindowResizeMixin
+ handleWindowResize:
, although you might end up with a lot of mixins. Again, depending on the size of the problem, you could even just have a single mixin that attaches to the events that have defined handlers/methods, handleWindowResize
, etc.
The mixins could even be implemented as an addon, although it kind of feels like a "native" implementation would be nice.
Maybe it would be useful to consider a Flux Store-like solution here? Like some kind of ReactEvents
store which wraps window-level events and emits synthetic events. Your components could subscribe and unsubscribe as they see fit.
onWindowResize: function(event) {
// do whatever you want in response to the resize event
},
componentDidMount: function() {
this.subscription = ReactEvents.subscribe(ReactEvents.constants.WINDOW_RESIZE, this.onWindowResize);
},
componentWillUnmount: function() {
this.subscription.remove();
}
A couple of additional data points on this - the demo in http://kentwilliam.com/articles/rich-drag-and-drop-in-react-js mentions having to drop out of react events to do document listeners for mouse movements.
I've got a small module I put together for handing hotkeys that reaches into a bunch of React internals in order to produce synthetic keyboard events for document key events: https://github.com/glenjamin/react-hotkey/blob/master/index.js - providing a neat top-level listener that can be subscribed to, but forcing components to manage their own subscriptions' lifecycle seems like a reasonable tradeoff to me.
An API to hook events into the component events, like @glenjamin described, to produce synthetic events would be a nice thing to have.
this.events.fromEvent( ... ) // events on this components DOM representatoin
this.events.fromEventTarget( ... ) // events from other targets like "document"
I would recommend a look at the Bacon.js wrappers. Maybe a fromCallback binder would be great, too?
It needs to be useable declaratively ( like other events )
// inside the component:
render: function() {
return (
<div onMyEvent={handler} > test </div>
);
}
// from outside of the component, too? ( i do not think so )
<MyComponent onMyCusomEvent={eventhandler} />
and when could it be registered?
// before the first rendering, because of the custom event attribute
componendWillMount: function(events) {
events.fromEvent( ... ).as('onMyEvent')
}
i think that is basically what @nick-thompson and @syranide were saying.
:+1: For React support window
-level events such as keydown
, keyup
, etc. for keyboard shortcuts.
:+1:
:+1:
:+1:
:+1:
👍
:+1:, I'd like something like this :
var Modal = React.createClass({
componentDidMount() {
React.addEventListener(document, "keyup", this.handleShortcuts)
},
componentWillUnmount() {
React.removeEventListener(document, "keyup", this.handleShortcuts)
},
handleShortcuts(eventObject) {
switch(eventObject.which) {
case 27:
this.props.hide()
break
// …
}
}
// …
})
:+1:
:+1:
:+1:
I'm looking for a solution like this as well! As in, having a standard DOM event called, say, MyWeirdEvent
and being somehow able to tell React to start managing it exactly as it does events like click
, with e.g.
<SomeComponent onMyWeirdEvent={handler} />
Currently the React event system feels quite exclusive of any 3rd party libs.
I think https://github.com/facebook/react/issues/285#issuecomment-65231515 is a pretty good idea, but its kind of messy.
I normally connect window/document level events to flux or pass it down the app tree.
It would be nice if you could pass an option to React.render to define it as the app entry point, and delegate those events to it.
class App extends React.Document {
handleResize() { this.forceUpdate() }
render() { return <div onDocumentResize={this.handleResize.bind(this)}/>; }
}
React.render(<App/>, document.body, {delegateEvents: true});
Kind of off topic, but this could be related to work making react handle being mounted on document.body function more sensibly... If it were safer to mount react on the document body you could delegate body events by default.
@nelix do events only propagate but not bubble in your proposal?
+1 to React.{add,remove}EventListener
. Provide the minimum api to hook into react's event system, and let third party libs build on this as they see fit.
Couldn't agree more with all above poster, +1
+1 to @brigand's suggestion
:+1:
+1
+1
+1
+1 (Currently using mousetrap)
:+1: But I'm really digging keymaster
componentDidMount: function() {
key('esc', this.onClose)
},
componentWillUnmount: function() {
key.unbind('esc', this.onClose)
},
onClose: function() {
console.log('awoiejf');
}
It'd be super rad to use ES7 decorators in a declaritive way like so:
class Component {
@globalEvent('click')
onGlobalClick(e) {
// handle window click
}
render() {
// render
}
}
This wouldn't actually be a logical approach to using decorators as they would just be mounted as functions on the class prototype. Not executed automatically when mounted. The only way this could be implemented (without giving it much thought) would be for the @globalEvent
decorator to register the method name with a predictable signature that could be searchable.
Something like:
@globalEvent('click')
onGlobalClick(e) { ... }
// compiled
_reactGlobalEvent{UUID}: handler
Then there would have to be some sort of mechanism internally that searches for similar keys:
/*
* @internal
*/
function bindGlobalHandlers(component) {
for (var method in component ) {
if (method.indexOf('_reactGlobalEvent') === 0) {
React.bindGlobalMethod(method);
}
}
}
Here is a potential implementation approach to the decorator:
function globalEvent(eventType) {
return function decorate(target, key, descriptor) {
function reactGlobalBind() {
React.globalEvent(eventType, descriptor.value)
}
Object.defineProperty(
target,
'_reactGlobalEvent' + Date.now(), // not a good UUID
{ value: reactGlobalBind }
);
}
}
class Component extends React.Component {
@globalEvent('click')
onGlobalClick(e) {
console.log('clicked');
}
}
This is basically an abuse of the decorator pattern
But it does make a nice implementation detail. Though it doesn't appeal to those who can't utilize the decorator pattern.
fwiw, I created an abuse of this pattern as an example: https://github.com/blainekasten/react-global-event-decorator/tree/master
@blainekasten different take on the same thing: https://github.com/brigand/react-global-event-method
I don't really like either of them though, specifically for the componentDidMount/etc issue. Maybe annotating the functions with the method decorators and still using a class decorator would be the way to go?
It'd still be cool to have this in core, mostly so we get the same event shimming benefits normal react events give.
The componentDidMount issue could be solved if this was an internal react implementation. But again, I'm not selling this as the way to go. Just a thought and experiment.
:+1: here.
API like onDocument<EventName>
and onWindow<EventName>
would be very handy. As well as imperative React.addEventListener(element, <EventName>, ...)
.
There are millions of articles currently which suggest to use simple <node>.addEventListener()
in the componentDidMount
hook in case one needs it, but no one clarifies that such things should be done through event delegation. At least helpers like React.addEventListener
could prevent redundant memory consumption in most of react plugins out the box.
Why not make use of the current JSX on event handlers and extend them? Anything that should be bound to the window, or the document, or global, should simply be prefixed, like so?
class Foo extends React.Component {
render() {
return (
<input
onKeyDown={this.onKeyDown} // Element event
onWindowKeyDown={this.onWindowKeyDown} // Window event
onDocumentSelectionChange={this.onDocumentSelectionChange} // Document event
onGlobalMouseWheel={this.onGlobalMouseWheel} // Global event
/>
);
}
}
This approach does not require lifecycle events, decorators, new syntax, new external APIs, is easy to use and understand, is clean and straightforward, etc. It's akin to appending Capture
to existing events.
Personally, it would be a better idea to improve React's internal event system, by providing new event types, instead of moving this logic to the component layer.
:+1: (decorators don't feel like the right pattern for this to me though)
On the topic of patterns for this, just how strongly-discouraged is context
these days? I just took a cue from redux
and wrote some code to handle CSRF tokens with an AuthenticityTokenProvider
component that passes down the data via context
and then a child AuthenticityToken
that receives it. Would a GlobalKeyEventsProvider
, GlobalMouseEventsProvider
, etc. be a solid direction?
+1
Having stumbled upon more or less the same issues outlined in this issue, I've created a little npm package called react-key-handler
. If this is something react team is willing to integrate directly into react, I'd be more than happy to help out.
Referencing my comment https://github.com/facebook/react/issues/284#issuecomment-218547730
Ad-hoc addEventListener
on an element is dangrous. If a nested component has React event (onClick
) with e.stopPropagation
, the event in addEventListener
won't be stopped.
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). But if I set it with window.addEventListener...
it stops successfully.
I think it's a bad behaviour.
Is React setting all synthentic events on window instead of specific DOM elements?
Here's the solution I came to, which works when you have your full page powered by React:
childContext
minHeight: "100vh"
)context
and subscribes to the eventsThe solution is declarative (you just use <ReactHigherEvent onClick={handleGlobalClick} />
). This is open-sourced here : https://github.com/bloodyowl/react-higher-event
@bloodyowl That won't work for the global events on window
like resize
.
Yes, of course, it can't put events that are not listened by React on the same phase, it solves the issue for use-cases like listening to a click outside & mousemove/up though.
Is this still being considered? It would be nice if React could somehow encourage users to do small tasks _optimally_ like when changing a header background color based on scroll position.
I encountered this on a project, and my instinct was to use setState
until I ran across this SO issue/answer relating to setState
performance: http://stackoverflow.com/a/35467176/1510454
This is the component I ended up with which also included support for passive event listening: https://gist.github.com/tribou/d405436286807eeff669ad4d909331f5
I've been wanting this as well. Lacing native event listeners into component lifecycle events feels very haphazard and opens up the potential for memory leaks & exceptions if steps aren't taken to properly tear down the listeners during unmount.
No near-term plans for this, sorry.
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 on document
, that's why you'd have to use SyntheticEvent#nativeEvent.stopImmediatePropagation()
:
class ExampleComponent extends React.Component {
render() {
return (
<div onKeyDown={(e) => e.stopPropagation()} />
)
}
}
document.addEventListener('keydown', () => {
alert('why does this still fire?')
})
ReactDOM.render(
<ExampleComponent name="react"/>,
document.getElementById('react')
)
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 those mouseup
events outside my component or even outside the browser (although React's event hub would capture those by listening on document
).
There are a lot of solution ideas - most of them are tied to DOM specific features like document
or window
. 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 can addEventListener()
and removeEventListener()
, but its callbacks will receive the SyntheticEvent
. The EventRoot
is created for every root react component (where instance._hostParent === null
. It should be accessible inside components by calling something like this.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
and bubble
order as well as stopPropagation()
. Everything you'd expect when listening on document
. But stopping propagation will be isolated to the specific React instance => Two react trees that listen on the EventRoot
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. The stopPropagation()
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. 😊
+1
With the advent of portals in React 16, it's the first time for me that React's event system has felt so dramatically different to that of the DOM. As I raised in #10962, the fact that events bubble through portals is very handy and so far seems to make logical sense, but is not something that works nicely with the current fallbacks to adding DOM events.
I think this divergence makes the need for such an API into React's event system even more relevant now.
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 which stopPropagation
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..
function onKeyDown(e) {
// Handle global keydowns. !Warning: may receive native or synthetic events
}
function onKeyDownNative(e) {
// Or whatever assertion works for your usecase, whatever is
// "outside" of the react tree.
if (e.target === document.body) {
onKeyDown(e);
}
}
// Wrap this around the entire app
class HotkeyListener extends React.Component {
componentDidMount() {
document.addEventListener("keydown", onKeyDownNative);
}
componentWillUnmount() {
document.removeEventListener("keydown", onKeyDownNative);
}
render() {
// Listens to any propagated synthetic keydown events
return <div onKeyDown={onKeyDown}>{this.props.children}</div>;
}
}
ReactDOM.render(
<HotkeyListener>
// This input will propagate and trigger global key event through the synthetic event handler
<input type="text" />
// This one will not
<input type="text" onKeyDown={(e) => e.stopPropagation()} />
</HotkeyListener>
, document.getElementById("app"))
Working demo on Codepen
(Wow, no progress in five years? Doesn't Facebook itself support keyboard shortcuts and dismissing popups by clicking outside them?)
I got bitten by this today, when I refactored something from listening to keypresses on an <input>
(in a React event) to using document.addEventListener
(in a native event) – I was calling setState
a lot, and suddenly not batching them made everything a lot laggier. I had to recover performance using ReactDOM.unstable_batchedUpdates
.
In current React, it would make sense to offer this as hooks, which also do update batching on all listeners of the type. This would be a significant value add, as React is serving as a hub for all of our own code and react packages we depend on... to provide efficient updates.
Regarding https://github.com/facebook/react/issues/285#issuecomment-253502585 in particular, we're switching React 17 to register events at the roots, which solves that particular case.
Most helpful comment
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. 😊