React: Mechanism to listen for all events

Created on 29 Jun 2015  Â·  23Comments  Â·  Source: facebook/react

Is there already such a mechanism? If so, can I help document it?

If not, I'll like to have a method or API that I could call to listen for all synthetic events. Something like...

React.Events.listenTo('*', function (event) { console.log(event) });

I'd like to be able to stopPropagation and preventDefault on those events, as well.

Most helpful comment

Because I don't want my event handling dependent on my rendering library. And business-as-usual sucks. Whatever happened to "rethinking best practices"?

All 23 comments

There is not. Why would you want such a thing?

For the same reason you listen to all events on the DOM. Also, so that event handling can be done in one place. It seems like a reasonable request given that you've essentially created your own event system on top of the DOM that is intended to match the W3C spec for events that I could do the same thing with your event system that you can do with the native DOM.

It sounds like you think this is a ridiculous request. Why do you feel that way?

How do you attach a single listener for all DOM events outside of React?

Admittedly with much pain:

'use strict';

const USE_CAPTURE = true;

function getEventNames (object) {
    let names = [];
    for (let name in object) {
        if (name.indexOf('on') === 0) {
            names.push(name.substr(2));
        }
    }
    return names;
}


export default function addDomEvents (emit) {
    let windowEvents = getEventNames(window);
    let documentEvents = getEventNames(document);
    let i, eventName;

    for (i = documentEvents.length - 1; i >= 0; i--) {
        eventName = documentEvents[i];
        document.addEventListener(eventName, emit, USE_CAPTURE);
        windowEvents.splice(windowEvents.indexOf(eventName), 1);
    }

    for (i = windowEvents.length - 1; i >= 0; i--) {
        eventName = windowEvents[i];
        window.addEventListener(eventName, emit, USE_CAPTURE);
    }

}

@nwwells It's done out of necessity, since React needs to create a perfect emulation of the DOM. Why would you need this for a client-side app, when business-as-usual is to bind to the particular handler you need at a given time?

Because I don't want my event handling dependent on my rendering library. And business-as-usual sucks. Whatever happened to "rethinking best practices"?

more on the "I don't want my event handling dependent on my rendering library"... I don't think the reason React does it is to create a perfect emulation of the DOM, but rather to make adding/removing event listeners easier/possible. At least that's why most people do it in my experience.

... to make adding/removing event listeners easier/possible

@nwwells Well yeah, that's necessary to recreate the DOM such that the React implementation is interacted with in a similar way.

So you think putting a global event catcher at the top level is a new best practice? I'm not criticizing your needs, just curious. It would be helpful to know what you're trying to do... catching keystrokes for shortcut purposes, etc?

That's just one of the things I'm trying to do. Essentially I'm creating an event stream that all events go to in the system. Not just DOM events, but server-side message events. All event handling can be coordinated from there.

One use case is handling key events for shortcuts or closing modals, etc. Another is calling preventDefault when any link is clicked that looks like an internal navigation event. I could also log all events as they hit the system and then recreate any given state based on a stored event stream.

@nwwells It sounds to me like you're building a framework, rather than an app. Our goal with React is to support the 99% use case for app developers, and ensure we have sufficient escape hatches for the remaining 1%. I don't see a huge need for app developers to listen for all events, since app developers generally want to associate specific behaviors with specific events, and therefore know which events they care about. Since you want to build a framework to manage your events, it sounds like the escape hatch is to use the DOM API, which will give you access to the underlying event system and let you respond in any way you want. At least, that's my two cents. I'll defer to @spicyj if he sees this issue differently.

It's interesting to me that you segregate framework building from app development. I'm actually building an app, so your inference isn't 100% accurate. However, the aspect of the app I'm currently working on could be considered framework building, in that I'm not interested handling in one particular use case, but rather a whole class of use cases in the most architecturally sane method I can figure.

In any case, I'm totally fine with you not giving me a mechanism to listen for all events. If it's more amenable you, I'd also be satisfied with totally disabling the React event system. There's been significant interference by said event system with what I'm trying to accomplish. I'm working through it, but it sure would be nice to either (1) know exactly what's happening by listening to all events, or (2) disable the event system and deal with the problems I'll have when React is expecting events to be fired.

Most frameworks try to bow out of all high-level abstractions, for various reasons. React, for instance, wants to bypass the DOM entirely and render pixels directly, bowing out of the browser APIs. Because bowing out is such a common desire for frameworks, trying to support all frameworks with a single high level API is a hopeless objective, so it's better for us to just allow the framework developers to escape out to a lower level API.

With regards to being able to disable the React event system: what is it about the React event system that is interfering with your code? What stops you from just ignoring (ie. not using) all React events?

Here's one I ran into:

<input value={this.props.someValue} />

If I specify an input and want to template in the value, React doesn't allow me to type because in it's event handling it sees that the value is controlled (i.e. value is passed in) and so sets the value of the input based on this.props.someValue. It does this because it expects you to update the value from the onChange handler. I tried updating the value of this.props.someValue from a separate (DOM-level) input event handler, but I couldn't see what the value of the text field was, because it had already been set to "" by React.

I've worked around the issue by specifying an onChange on every input (technically I'm using valueLink), but I'd rather not do that, since it breaks my architecture.

Hmm, interesting. cc @spicyj @syranide

What would an ideal fix look like for this? Disabling the React handling of this event would lead to the data in the text box becoming out of sync with the value provided (this effectively turning the component into an uncontrolled component, which is wrong behavior from a React perspective).

Obviously you could use uncontrolled inputs, but we should support controlled components in order to preserve idiomatic React. For controlled components, it seems like having your framework specify an onChange event handler (using the React event system) is the correct behavior, since any other solution would introduce race conditions or data consistency issues. I don't really see any other way around it :/.

Ideas?

There is not. Why would you want such a thing?

I agree. IMHO the power of React comes from the isolation of components and the readability it offers. I'm personally not at all a fan of being able to listen for most events on unrelated ancestors (onSubmit, onChange, etc), I see no technical reason why you want that other than being "less code". This is more of the same, it looks sensible because people have previous experience with HTML, I don't think many would suggest such a feature for any other frontend.

Don't get me wrong I don't necessarily mind there being shortcuts, but when you're not significantly experienced it's hard to know good from bad and these shortcuts look like actual solutions (especially to those familiar with jQuery) rather than the messy problems they tend to devolve into. It pushes you outside the "pit of success".

I've worked around the issue by specifying an onChange on every input (technically I'm using valueLink), but I'd rather not do that, since it breaks my architecture.

In my opinion, that's absolutely the right approach (onChange on every input).

But... you're free to move onChange to context and provide custom components to go with it if you have a special use-case in-mind (which seems largely equivalent to what you want). There's nothing wrong with that, if you're OK with the complexity and find the trade-offs acceptable. But I think React does just fine being as simple as it is (and could easily shed a few of the HTML quirks). React already provides the tools needed to build such (frontend-independent) abstractions, it only introduces unnecessary complexity (for everyone) to have it in core.

@jimfb — I don't mind React handling events, but rather that there is no way to tap into that event system and see all the events. I actually want a controlled input (in that I want to set it's value based on props) but I don't want to have to set that value in the onChange handler of any given component.

@syranide — so, are you rejecting my use cases I outlined above out of hand? Also, I'm not looking for a shortcut, but rather a generalized mechanism. And using the context mechanism you refer to would essentially bar me from using anyone else's components unless I customized each one to use my custom version of the elements. Unless I could somehow do it in such a way that all components would be forced to you my implementation of the "input" component, for example?

so, are you rejecting my use cases I outlined about out of hand?

Not at all, but just because there is another way of doing something doesn't mean that it's an acceptable trade-off. You're talking about extending core in ways that seem objectionable to me, when context already largely solve your problem.

You expect React to formalize an API that's somewhat unique to HTML DOM. Not all frontends even have a concept of bubbling, you listen to controls individually.

And using the context mechanism you refer to would essentially bar me from using anyone else's components unless I customized each one to use my custom version of the elements. Unless I could somehow do it in such a way that all components would be forced to you my implementation of the "input" component, for example?

Just provide a wrapper factory (or w/e). Components can always introduce inconsistencies or implement entirely different input methods (canvas, etc) that you would not be able to support as you intended. Wrapping allows you to interop with _any_ component that has a compatible API.

To be clear, I don't necessarily mind an event system exposing this functionality with you knowingly accepting the consequences (you're free to circumvent all the React stuff and hook the DOM directly). But the "React DOM event system" is an internal implementation detail, it should not be confused with the React API which should be generic and independent of the underlying frontends.

You expect React to formalize an API that's somewhat unique to HTML DOM. Not all frontends even have a concept of bubbling, you listen to controls individually.

Well, FWIW, I would be doing this on any front-end, not just HTML DOM. To me, this is part of what got me excited about React, with the uni-directional flow of data. In fact, I'm not really asking for bubbling at all, but rather a single place to listen for all events generated by React. Every front-end generates events.

Just provide a wrapper factory (or w/e).

Can you point me to an example of what you mean?

(you're free to circumvent all the React stuff and hook the DOM directly)

Do you consider passing values into an input element part of "all the React [event] stuff"? I would happily bypass React's event system entirely, but I have been unable to do that without working around the ways in which React's components are tied to it's event system.

Well, FWIW, I would be doing this on any front-end, not just HTML DOM. To me, this is part of what got me excited about React, with the uni-directional flow of data. In fact, I'm not really asking for bubbling at all, but rather a single place to listen for all events generated by React. Every front-end generates events.

There are frontends implementing just per-control callbacks, having no central event system to subscribe to or simply having an incompatible API. I mean many basic game frontends only provide a global mouse/keyboard API, there is no bubbling or inherent concept of controls and focus. But, context and wrappers could handle all this just fine.

Can you point me to an example of what you mean?

Something along the lines of the code below (just an example), could probably be turned into a helper/factory too.

class MyMagicInputWrapper {
  ...context stuff...
  render() {
    return (
      <MagicInput
        value={this.context.myForm.values[this.props.name]}
        onChange={(value) => this.context.myForm.onChange(this.props.name, value)}
      />
    );
  }
}

Do you consider passing values into an input element part of "all the React [event] stuff"?

Not sure what you mean.

I would happily bypass React's event system entirely, but I have been unable to do that without working around the ways in which React's components are tied to it's event system.

Why do you want that? It sounds like you're going against the intentions of React just to go your own way, what benefits do you see versus just using React as it was designed/intended?

I'm wondering what your intentions are, it kind of sounds like your building your own UI framework on-top of the React UI framework, rather than using React as it was intended.

I would happily bypass React's event system entirely, but I have been unable to do that without working around the ways in which React's components are tied to it's event system.

The only way (that I can think of right now) that DOM components are tied to the event system is in value/checked handling. If you use defaultValue/defaultChecked then React won't do anything special.

OK. Thanks.

This is probably useless after 5 years, but I was just reading it because I also would love to be able to intercept all events, in my case to measure selectors recomputations/time on each event. Performance tracking lets say.

I was thinking that a way to do this if there's still no API to intercept all events would be to use a custom JSX pragma function (https://babeljs.io/docs/en/babel-plugin-transform-react-jsx)

  • intercept all React.createElement call
  • wrap all values passed to well-known event attributes like onClick, onMouseOver etc. with a wrapper function to do whatever you want.

I haven't tried it, but sounds like it should to the trick if you use JSX.
Of course it has an overhead, in my case I can use it just for dev purpose. Sort of a cross-cutting concern.

@javierfernandes This is actually exactly what I have been doing for a long time as a workaround for this.
I was monkey patching React.createElement with new props, that actually wrapped some event handler with a wrapper function.

However, after switching to the new JSX transformer this does not seem to work anymore. Props now seem to be stripped out of any event handlers such as "onPointerDown".

Was this page helpful?
0 / 5 - 0 ratings