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

[react/primitive] React 18 – Flush discrete CustomEvent dispatch calls #1378

Merged
merged 8 commits into from
May 10, 2022

Conversation

andy-hook
Copy link
Collaborator

@andy-hook andy-hook commented May 9, 2022

Fixes #1287
Supersedes #1292

Big thanks to @Andarist for providing great insight into a resolution. This change is mostly equivalent though with the additional goal of providing a clear heuristic for when this is necessary and applying the change to all applicable code.

Context

One of the core changes in React 18 is the introduction of consistent batching across all event handlers, this differs from 17 where only certain events would be subject to batching. As a result, certain state updates are now batched where before they were not, creating issues like those seen in #1287

Event priorities

React assigns priorities to updates based on the type of user interaction (discrete, continuous, default), discrete is an important distinction as it allows updates from those handlers to be made immediately. As stated in #1292 (comment) the way that React determines this priority is by usage of window.event.

Because custom event types are unable to be assessed in this way, they fall through to default. In most cases this wouldn’t be a problem, but in the case of ContextMenu our code structure is such that there is an implicit expectation for one update to commit before another.


Why this can be problematic

In the case of ContextMenu, the incorrect behaviour is caused by the unexpected batching of updates in pointerdown and contextmenu, while the execution order remains correct (pointerdown -> contextmenu), the resulting state change is batched in 18, resulting in no update to open. As the effect cleanup is no longer cycled by moving through false -> true pointer-events: none; does not get removed and our handler in contextmenu is blocked.

You can see and compare the behaviour in this example.



A heuristic for consistency

Given our understanding of how React splits priorities for updates, and that the intention for immediate user interaction i.e. discrete events is to not defer, dispatchDiscreteCustomEvent should be used whenever a custom event type is dispatched from a discrete handler. This is the recommendation from the core team.

Internal p
riorities for these types can be found here

Use of Event vs CustomEvent

I’ve changed every usage of the the Event constructor to be CustomEvent when a custom type is provided, this is a style change as either constructor is susceptible (it’s important to remember priority is inferred by the type passed, not which constructor used) the hope being that staying consistent will make deciding when to use dispatchDiscreteCustomEvent easier.



e.g.


// 

dispatching a click


target.dispatchEvent(new Event(‘click’))





// 



dispatching a custom type


target.dispatchEvent(new CustomEvent(‘customType’))





// 





dispatching a custom type within a discrete handler


onPointerDown={(e) => dispatchDiscreteCustomEvent(e.target, new CustomEvent(‘customType’))}

@andy-hook andy-hook changed the title Flush discrete CustomEvent dispatch calls [core/primitive] React 18 – Flush discrete CustomEvent dispatch calls May 9, 2022
@andy-hook andy-hook marked this pull request as ready for review May 9, 2022 12:56
packages/core/primitive/src/primitive.tsx Outdated Show resolved Hide resolved
packages/core/primitive/src/primitive.tsx Outdated Show resolved Hide resolved
packages/core/primitive/src/primitive.tsx Outdated Show resolved Hide resolved
cypress/integration/ContextMenu.spec.ts Show resolved Hide resolved
@@ -23,7 +23,8 @@
"@radix-ui/react-use-layout-effect": "workspace:*"
},
"peerDependencies": {
"react": "^16.8 || ^17.0 || ^18.0"
"react": "^16.8 || ^17.0 || ^18.0",
"react-dom": "^16.8 || ^17.0 || ^18.0"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will have to re-review these with the change of package so I'm skipping for now.

packages/react/toast/src/Toast.tsx Show resolved Hide resolved
@benoitgrelard
Copy link
Collaborator

♥️ the description btw!

.yarn/versions/ce619930.yml Outdated Show resolved Hide resolved
packages/core/primitive/src/primitive.tsx Outdated Show resolved Hide resolved
packages/react/collection/package.json Show resolved Hide resolved
yarn.lock Show resolved Hide resolved
@benoitgrelard benoitgrelard merged commit 4d3346a into main May 10, 2022
@benoitgrelard benoitgrelard deleted the discrete-custom-events branch May 10, 2022 16:21
@benoitgrelard benoitgrelard changed the title [core/primitive] React 18 – Flush discrete CustomEvent dispatch calls [react/primitive] React 18 – Flush discrete CustomEvent dispatch calls May 10, 2022
@Andarist
Copy link
Contributor

A great level of detail has been added here in the description and in the comment in the code 👍

luisorbaiceta pushed a commit to luisorbaiceta/primitives that referenced this pull request Jul 21, 2022
…ls (radix-ui#1378)

* Flush dispatch inside discrete handlers

* Versions

* Annotate

* Move util, update deps

* Update annotation

* Versions

* Fix example in annotation

* Rervert format in primitive, versions
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[ContextMenu] Native context menu incorrectly appears after second click
3 participants