Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Play Nicely with The DOM Event System (because it's legacy anyway) #4751

Open
sebmarkbage opened this issue Sep 1, 2015 · 11 comments
Open
Labels
React Core Team Opened by a member of the React Core Team Type: Big Picture

Comments

@sebmarkbage
Copy link
Collaborator

Currently React implements its own plugin system and event bubbling rules. This is important for systems like the responder system since the DOM event system is incapable of implementing proper event negotiation support.

However, on desktop, we don't really take full advantage of our custom event system other than to polyfill mouseenter/leave and implement more bubbling.

There are plenty of other things that are wrong with the DOM but instead of patching it, maybe it would be better to build a new view system on top of the DOM that integrates with the custom event system. Similarly to how React Native handles things.

That way, the lowest level could just do what the DOM would do, however broken it may be.

Question though: How do we handle things like onChange?

cc @spicyj and @syranide since I know you always wanted this.

@syranide
Copy link
Contributor

syranide commented Sep 1, 2015

I'm not entirely sure what you're suggesting and I'm not familiar with ReactNative.

I see two versions of ReactDOM; one being the barebones "as-is DOM" implementation where you expose the DOM as it is (and play nice with external events). But I'm not sure what users can realistically do to fix some of the more broken aspects of the DOM event system in this model though? Perhaps it doesn't really matter that much...

The other is a more reimagined implementation of the DOM (managed inline styles, etc). It would not map DOM events 1:1, some events no longer bubble (like onChange), callbacks don't receive a generic event object and instead are only provided the values that are of immediate interest (i.e. only value for onChange, perhaps selection too, etc), etc. That is, expose events in a way that makes sense to isolated React apps and largely hide the DOM details and quirks.

Both have their own merits, with the second sounding more of a community project to me.

Perhaps this made no sense to what you're suggesting though :)

@Gozala
Copy link

Gozala commented Sep 11, 2015

As someone who has being struggling with integrating non-standard DOM events into react app I would like to share my humble opinion on the subject.

I find it really unfortunate that react attempts to normalize DOM event system because:

  1. It comes at the cost of limiting events you can hook into (sure you could add your listeners on componentDidMount and remove them on componentWillUnmount but that is far from ideal specially when your components are otherwise pure functions. In that regard I find hooks abstraction used by virtual-dom to be a lot more composable.
  2. It also becomes difficult to normalize events in a different way.

I think it would be a lot better if React exposed DOM events as is and also provided custom event system separate from it. It could be dealt simply by choosing different naming convention for event handlers or IMO it would be even better to employ similar hooks abstraction as virtual-dom as it makes it really easy to define & share custom hooks like onMount(doSomething) onNextAnimationFrame(doSomething) without wrapping those up into container components.

@sebmarkbage
Copy link
Collaborator Author

The problem is that I think that the DOM events and a custom event system will never integrate well. You can already see this in frameworks since the jQuery days. You can also see this in the DOM itself where events doesn't cooperate between iframes. They're whole boxes that steal the events and doesn't bubble for example.

For example, the responder event system will need to conditionally block certain events from occurring elsewhere.

That's why I think that a good custom event system is eventually going to be mutually exclusive with integrating well with the DOM event system. Basically, mixing two in the same box/window/frame is probably going to lead to a bad time regardless and is likely not going to be supported.

It's easy to say that virtual-dom does the right thing because it just offloads the problem to someone else to solve. Once you start trying to solve these interaction problem it gets messy.

Custom hooks into a global namespaces becomes a compatibility nightmare when the same name is used in slightly different versions and slightly different packages. Great if you write all your own components but not great if you're trying to cooperate in an ecosystem or large company. We could potentially make scoped extensions somehow, but it seems better to just wrap your commonly used components. Opting into using <Text /> instead of <span /> shouldn't be a big price to pay for compatibility.

@Gozala
Copy link

Gozala commented Sep 12, 2015

The problem is that I think that the DOM events and a custom event system will never integrate well. You can already see this in frameworks since the jQuery days. You can also see this in the DOM itself where events doesn't cooperate between iframes. They're whole boxes that steal the events and doesn't bubble for example.

I was not necessarily suggesting integrating them but rather providing two levels for users to hook. For most common cases higher level custom event system will work fine, this is also what we prefer to use when possible. But if there is something that react team has not added support yet it is pretty much impossible to hook up into that higher level tire.

It's easy to say that virtual-dom does the right thing because it just offloads the problem to someone else to solve. Once you start trying to solve these interaction problem it gets messy.

What I meant to say it provides good low level foundation that you can build say custom event system on top and ship all integrated solution. But it will also allows custom additions to the system. Sure it can get messy if you try to integrate hooks that don't work with each other, but I'd argue it's better than having no way to extend.

Custom hooks into a global namespaces becomes a compatibility nightmare when the same name is used in slightly different versions and slightly different packages. Great if you write all your own components but not great if you're trying to cooperate in an ecosystem or large company.

I don't follow here what do you refer to when you say "global namespace" ? Hooks don't reserve any names you box prop values with hooks so there is no naming conflicts as it's by identity rather than name. Maybe I misunderstood what you said though.

We could potentially make scoped extensions somehow, but it seems better to just wrap your commonly used components. Opting into using instead of shouldn't be a big price to pay for compatibility.

It's not always that easy I'm afraid. For example we use bunch of non standard DOM swipe events (see #4645 there are examples) to allow interaction with various UI entities. There is no single container makes sense across the board UI entities it's being used so boxing event hooks into special component does not quite work all that well. There is also cases where we use other custom events with swipes so you either add yet another container or start mixing non of which is a great solution. At the end of the day we end up implementing poor mans version of Hooks on top of react. Which works sort of but there are number of limitations that make it less ideal. I'm happy to share code illustrating these if there is an interest.

@sebmarkbage
Copy link
Collaborator Author

I was not necessarily suggesting integrating them but rather providing two levels for users to hook.

@Gozala Yea, that is basically what this issue is meant to address. You would have two different View systems. One that is just pass-through to the DOM, and one that is an abstraction layer with a built-in richer event system.

Hooks don't reserve any names you box prop values with hooks so there is no naming conflicts as it's by identity rather than name.

By global name I mean the namespace on <htmlelement ... />. All the property names on "props", for HTML elements share the same namespace globally. I meant that if you claim the name MozSwipeGestureStart for all events on the page, and add custom logic to it, and then someone else tries to do the same, there is no way to scope them differently.

We could potentially have a whitelist for things that doesn't get special logic. E.g. just SimpleEventPlugin is allowed. That way it is not possible for two different component systems to collide in terms of logic. It is either on or off. However, we might need to break them between versions to be able to intercept the event system for events that we have special logic around, or special synthetic events.

There is no single container makes sense across the board UI entities it's being used so boxing event hooks into special component

You can use mixins to reuse the same logic on all your base components. You can also have a component that wraps the bubbled events inside your base components.

E.g. return <EventHandler onSwipeGestureStart={this.props.onSwipeGestureState}><div ... /></EventHandler>

If you're like Facebook/Netflix/Yahoo etc. and build your abstractions on TOP of the DOM this is easy because you end up with a few bottom layer primitives that you build everything else on. However, if you're like Mozilla/Polymer and build all your abstractions into the DOM, this is a bit of a pain because you have to wrap them every time you add a new abstraction.

The Facebook/Netflix/Yahoos of the world can't build our abstractions into the DOM because we can't wait for a new cross-browser event that plays nicely with the rest of the DOM event system. E.g. Apple's "3D Touch" would need some integration with other DOM events like mousedown so we can't just have them pass straight through.

However, it seems fair that we should decouple that event system so that it become easier to use DOM-heavy abstractions.

@Gozala
Copy link

Gozala commented Sep 14, 2015

By global name I mean the namespace on <htmlelement ... />. All the property names on "props", for HTML elements share the same namespace globally. I meant that if you claim the name MozSwipeGestureStart for all events on the page, and add custom logic to it, and then someone else tries to do the same, there is no way to scope them differently.

I think understand what you were saying, it's just I think you're misunderstanding how hooks actually work though. The prop name is irrelevant in that system what is relevant is the Hook with which property value is boxed with, here is an example:

html.div({
  foo: onClick(function(event) {
    alert('hello hook');
  })
})

In fact you could have two different event handlers for the same event as well, just add bar: onClick(fn) to that and that's it. In terms of react and jsx I can see how it can be tricky to transition to such an API without old one though.

One thing possibly jsx could desugar to could be:

<Foo onClick ={handler} />


Foo({[onClick.id]: onClick(handler)})

Assuming hooks use id's that are symbols (or namespaces strings you should be able to avoid conflicts). Elm actually uses lists instead of maps to avoid naming conflicts all together.

We could potentially have a whitelist for things that doesn't get special logic. E.g. just SimpleEventPlugin is allowed. That way it is not possible for two different component systems to collide in terms of logic. It is either on or off. However, we might need to break them between versions to be able to intercept the event system for events that we have special logic around, or special synthetic events.

Just to be clear I was suggesting hooks as the low level API that on could build a higher level event system on top. I suspect higher level API will likely still have naming conflicts issue unless you do want to completely rework the API.

You can use mixins to reuse the same logic on all your base components. You can also have a component that wraps the bubbled events inside your base components.

E.g. return <div ... />

This is more or less what we do, but find it really cumbersome because I can see only two options in how EventHandler may work:

  1. Have an associated element in the DOM which makes styling job more complex and causes some of the handler to be one the container while all others on the contained element.
  2. Make EventHandler a component that returns it's child back and then use hooks to register listeners on the child after it's mounted. But again some of the standard events would need to be set on the child which is still not ideal.

Things are actually little worth for us because we also use custom elements / attributes some of which need to be set before node is in the tree otherwise they don't work and combination of these issues makes it really difficult to pull it of.

If you're like Facebook/Netflix/Yahoo etc. and build your abstractions on TOP of the DOM this is easy because you end up with a few bottom layer primitives that you build everything else on. However, if you're like Mozilla/Polymer and build all your abstractions into the DOM, this is a bit of a pain because you have to wrap them every time you add a new abstraction.

The Facebook/Netflix/Yahoos of the world can't build our abstractions into the DOM because we can't wait for a new cross-browser event that plays nicely with the rest of the DOM event system. E.g. Apple's "3D Touch" would need some integration with other DOM events like mousedown so we can't just have them pass straight through.

However, it seems fair that we should decouple that event system so that it become easier to use DOM-heavy abstractions.

The reason I want a better hooks to the DOM is in fact so that we (at least one team at Mozilla) would like could to higher level abstractions that expose more APIs that standard DOM does. It's just really difficult to do today given the API at hand. I suspect that Facebook / Netflix / Yahoo don't run into the same problems as it's less likely they are experimenting with a custom DOM APIs that aren't implemented anywhere yet.

Anyway I did not implied that React should do what virtual-dom library does, I was mostly trying to say Yes please have a two layered event architecture so we could add support to events that react does not.

@sebmarkbage
Copy link
Collaborator Author

@Gozala Thanks a lot. This is great feedback! It's a slightly different take (and more thoroughly thought through) than we've heard before.

I've considered having a special element that can register event listeners but doesn't actually contribute to the DOM.

<tr><EventHandler onClick={...}><CustomTd /></EventHandler></tr>

I've been wanting that for a while and it would be trivial for us to add since we have our own custom event bubbling but without that it's tricky. It would be nice to have special DOM elements that are non-semantic contributions as well. That's one of the problems we have with Web Components.

@Gozala
Copy link

Gozala commented Sep 14, 2015

@Gozala Thanks a lot. This is great feedback! It's a slightly different take (and more thoroughly thought through) than we've heard before.

I'm glad it I could help ;)

I've considered having a special element that can register event listeners but doesn't actually contribute to the DOM.

I've been wanting that for a while and it would be trivial for us to add since we have our own custom event bubbling but without that it's tricky.

That would be really great! I actually tried to do a version of EventHandler that would attempt to pass on it's handlers to a child but I could not quite figure out a clear way to do since setProps is deprecated.

That's one of the problems we have with Web Components.

Another alternative could be something along the lines what we use to workaround whitelist of custom event / attribute / properties. We basically have a function that takes a tagName and a map of custom prop (which may also remind you of earlier mentioned hooks). React component on render just creates an element with a given tagName and invokes custom props hooks from lifetime methods: https://github.com/mozilla/browser.html/blob/master/src/common/element.js

This is how it looks to use this API:
https://github.com/mozilla/browser.html/blob/master/src/browser/iframe.js#L40-L143

On major limitation that we still struggle with is that (which is on the part Gecko limitation) is that some attributes / properties need to be set up before node is injected into a document (In the end we end up using DOMProperty.injectDOMPropertyConfig for those):
https://github.com/mozilla/browser.html/blob/master/src/browser/iframe.js#L18-L25

Maybe React could expose something along those lines ? Note that this would avoid name collision as props are scoped to a custom defined elements (unless your custom prop does already collides with name taken by react). This also handles custom anything problem not just events or attributes. Composition is not ideal but little better than with plain containers cause some of the custom props implementations could be reused (see https://github.com/mozilla/browser.html/blob/master/src/browser/iframe.js#L53)

P.S.: @vjeux mentioned you've being thinking about some other stuff that we've being struggling with. I personally have being trying to move all of the app code into web-worker to keep only a renderer in the main thread. I'd be happy to chat about that if there is a good venue for it.

@syranide
Copy link
Contributor

I've been wanting that for a while and it would be trivial for us to add since we have our own custom event bubbling but without that it's tricky. It would be nice to have special DOM elements that are non-semantic contributions as well. That's one of the problems we have with Web Components.

That seems like a too generic approach to me, especially when you consider overhead of components. It also seems to me that it kind of breaks down in non-trivial cases.

<TouchHandler ...>
  <MouseHandler ...>
    <PenHandler ...>
      <Comp />
    </PenHandler>
  <MouseHandler>
<TouchHandler>

That doesn't seem very nice at all. It also breaks component isolation in a way, I shouldn't reach inside opaque components, but I should attach event handlers without their permission?

Wouldn't it make more sense with an approach along the lines of:

<div events={[TouchHandler(...), MouseHandler(...), PenHandler(...)]} />

That way generic components could instead pass the events along to wherever it should go. Say you have a button with a decorative frame, the decorative frame shouldn't respond to events but with the proposed solution there doesn't seem to be a way around it?

@sebmarkbage
Copy link
Collaborator Author

That's the problem with event bubbling in general and this doesn't make that worse.

It is just a hassle to add listeners to every little node that may or may not be capturing something.

You can use pointer-events: none to make non-hit target surfaces like your border.

I think that this is a problem that goes much deeper into the DOM model.

Ideally you would render hit targets completely separately from rendering IMO.

On Sep 15, 2015, at 12:51 AM, Andreas Svensson notifications@github.com wrote:

I've been wanting that for a while and it would be trivial for us to add since we have our own custom event bubbling but without that it's tricky. It would be nice to have special DOM elements that are non-semantic contributions as well. That's one of the problems we have with Web Components.

That seems like a too generic approach to me, especially when you consider overhead of components. It also seems to me that it kind of breaks down in non-trivial cases.

<TouchHandler ...>
<MouseHandler ...>
<PenHandler ...>




That doesn't seem very nice at all. It also breaks component isolation in a way, I shouldn't reach inside opaque components, but I should attach event handlers without their permission?

Wouldn't it make more sense with an approach along the lines of:

That way generic components could instead pass the events along to wherever it should go. Say you have a button with a decorative frame, the decorative frame shouldn't respond to events but with the proposed solution there doesn't seem to be a way around it?


Reply to this email directly or view it on GitHub.

@fabslab
Copy link

fabslab commented Sep 30, 2015

Having an event system that isn't specific to the DOM would also help non DOM renderers easily add their events to React. Brought this up in reactjs/react-art#67

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
React Core Team Opened by a member of the React Core Team Type: Big Picture
Projects
None yet
Development

No branches or pull requests

6 participants