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

How to wrap events in React 16 #11115

Closed
tolmasky opened this issue Oct 5, 2017 · 8 comments
Closed

How to wrap events in React 16 #11115

tolmasky opened this issue Oct 5, 2017 · 8 comments

Comments

@tolmasky
Copy link

tolmasky commented Oct 5, 2017

Per the discussion from React 16 RC, I was asked to open a separate issue regarding opening up the event system in React. We currently use:

require("react-dom/lib/EventPluginUtils").executeDispatchesInOrder

But this disappeared in React 16, which is blocking our ability to upgrade to it.

Basically we grab executeDispatchesInOrder in order to wrap it so a certain piece of code can fire afterward. I was asked here why we don't just wrap addEventListener instead, and at least at the time, I believe my experiments showed that this didn't work because React seemed to firing its internal synthetic events at a later time (possibly batching them? I don't know). If there is a new hacky way to do this same thing, I'm happy to do that and punt on this question.

Do you want to request a feature or report a bug?
Depends, certainly used to work, so in that sense a bug, but would require API creation perhaps, so maybe a feature?

What is the current behavior?
No access to event firing.

What is the expected behavior?
Some way to inject code to fire after events fire.

Which versions of React, and which browser / OS are affected by this issue? Did this work in previous versions of React?
React 16, solution existed in React 15 and down.

@gaearon
Copy link
Collaborator

gaearon commented Oct 5, 2017

Basically we grab executeDispatchesInOrder in order to wrap it so a certain piece of code can fire afterward

Can you describe this in more detail?

@tolmasky
Copy link
Author

tolmasky commented Oct 5, 2017

Sure, we use immutable.js with our own Cursor library to handle everything through a single state atom that gets passed to the root element. Then, event handlers may do something like this (a bit pseudo-codeish as we have our own custom shouldComponentUpdate and whatnot):

Counter = function ({ count })
{
    return <div onClick = { () => set(count, deref(count) + 1) } >{ deref(count) }</div>
}

count here is a Cursor (so actually represents something like { keyPath:"path/to/count/from/root" }. Then, we wrap executeDispatchesInOrder with something like this:

const executeDispatchesInOrder  = require("react-dom/lib/EventPluginUtils").executeDispatchesInOrder;

require("react-dom/lib/EventPluginUtils").executeDispatchesInOrder = function ()
{
     previousRoot = deref(rootCursor);
     executeDispatchesInOrder.apply(this, arguments);
    
     // Something in event handling triggered a change! 
     if (compare(deref(rootCursor), previousRoot) !== 0)
         React.render(<App root = { rootCursor } />);
}

After this, things behave as expected: asynchronous changes to keyPaths leading from the root cursor trigger re-renders that appropriately through immutable.js comparison only invalidate the expected components (aka everything along the parent-path to the leaf change).

EDIT: Clarified a few things in the example since it kind of relied on knowledge from our internal workings.

@gaearon
Copy link
Collaborator

gaearon commented Oct 5, 2017

I would expect that wrapping browser event handlers should still let you do this.

@tolmasky
Copy link
Author

tolmasky commented Oct 5, 2017

Just tried onClick and onScroll, and both of those do indeed work. I'm trying to recall whether it was a specific event that diverged and required this technique back when we wrote it 2 years ago with React 0.12.2. Off the top of your head, do you happen to know if either 1) it was different in an earlier version of React, or whether 2) there are certain events that aren't 1-to-1 matched with a native call to addEventListener, perhaps some event that only React generates synthetically or something? In other words, if we switch to just wrapping addEventListener, are there any events we won't see go through? If not, then I'm happy to consider this closed.

@aweary
Copy link
Contributor

aweary commented Oct 5, 2017

@tolmasky there are a few events which can be built synthetically based on other events, specifically:

  • change is almost often synthetic, composed of events like blur, focus, input, selectionchange, etc.
  • mouseenter and mouseleave are calculated using mouseout and mouseover events
  • beforeinput is pretty complicated, lots going on there
  • select events depend on a few key and mouse events along with selectionchange

Other than that, most other events should correspond to browser events that you can listen for. You could also potentially listen for a dependent event for these synthetic events (for example, listen to input or keydown for change events).

@gaearon
Copy link
Collaborator

gaearon commented Oct 5, 2017

But synthetic events still always happen inside of browser events on the call stack, right? Even if names don't match up.

@aweary
Copy link
Contributor

aweary commented Oct 5, 2017

@gaearon yes, synthetic events should be extracted and dispatched inside the event listener that's registered for whatever dependent event is triggered.

@sebmarkbage
Copy link
Collaborator

The idea is to possibly move event from this synthetic path and use browser events directly at some point. At that point, it'll be required to wrap this in events.

I wonder if there's a better way to solve this for you though.

The strategy of calling ReactDOM.render outside the batch will for example by-passes the optimizations we have in mind for asynchronous rendering. The idea is to use event wrappers as a hint to what priority level the default update is associated with, or to flush synchronously only the shortest path to the textbox that issued the event (in the case of controlled components).

So I wonder if we can solve your use case in a more integrated way that still lets React do its normal work.

What is the ultimate goal of this design? Is it to avoid coupling any logic in the set function with React?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants