Declarative API for installing global DOM event handlers #285
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…
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.
For React support window
-level events such as keydown
, keyup
, etc. for keyboard shortcuts.
, 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
// …
}
}
// …
})
Keyboard event handlers no longer invoked if document.body get's focus. #2846
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 #285 (comment) 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.
+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.
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
}
}
Decorator Comment Edit:
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');
}
}
Full discretion
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.
Investigate best way to interact with React. #6
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.
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?
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.
#284 reminded me that one thing I've sometimes wanted is to install a handler on window for
keypress
(for keyboard shortcuts) orscroll
. Right now I can just dowindow.addEventListener
incomponentDidMount
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.)