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

Sync hydrate discrete events in capture phase and dont replay discrete events #22448

Merged

Conversation

salazarm
Copy link
Contributor

@salazarm salazarm commented Sep 27, 2021

Summary

This PR makes it so that discrete events are not replayed because there is a lot of ambiguity regarding the priority of the events and trying to enforce the order of the discrete events leads to being suboptimal with CPU. Instead we attempt to synchronously hydrate discrete events in the capture phase of the event (on the React root) in time for their capture and bubble listeners to fire naturally.

Continuous events are unaffected and will continue being sync hydrated in bubble phase if possible and replayed.

How did you test this change?

Jest.

I left the code ungated to make it easier to review but after review I will fork the codepaths and tests and put them behind a flag so that we can test the changes.

@facebook-github-bot facebook-github-bot added the React Core Team Opened by a member of the React Core Team label Sep 27, 2021
@sizebot
Copy link

sizebot commented Sep 28, 2021

Comparing: a4bc8ae...c10215e

Critical size changes

Includes critical production bundles, as well as any change greater than 2%:

Name +/- Base Current +/- gzip Base gzip Current gzip
oss-stable/react-dom/cjs/react-dom.production.min.js = 130.05 kB 130.05 kB = 41.34 kB 41.34 kB
oss-experimental/react-dom/cjs/react-dom.production.min.js = 132.88 kB 132.88 kB = 42.31 kB 42.30 kB
facebook-www/ReactDOM-prod.classic.js +0.27% 412.54 kB 413.66 kB +0.22% 76.28 kB 76.44 kB
facebook-www/ReactDOM-prod.modern.js +0.28% 401.13 kB 402.24 kB +0.21% 74.56 kB 74.71 kB
facebook-www/ReactDOMForked-prod.classic.js +0.27% 412.54 kB 413.66 kB +0.22% 76.28 kB 76.45 kB

Significant size changes

Includes any change greater than 0.2%:

Expand to show
Name +/- Base Current +/- gzip Base gzip Current gzip
facebook-www/ReactIs-dev.modern.js +1.55% 9.99 kB 10.14 kB +1.54% 2.66 kB 2.70 kB
facebook-www/ReactIs-dev.classic.js +1.55% 9.99 kB 10.14 kB +1.54% 2.66 kB 2.70 kB
facebook-www/JSXDEVRuntime-dev.modern.js +0.34% 45.57 kB 45.72 kB +0.33% 12.88 kB 12.92 kB
facebook-www/JSXDEVRuntime-dev.classic.js +0.34% 45.57 kB 45.72 kB +0.33% 12.88 kB 12.92 kB
facebook-www/ReactTestUtils-dev.modern.js +0.30% 51.35 kB 51.50 kB +0.24% 14.40 kB 14.43 kB
facebook-www/ReactTestUtils-dev.classic.js +0.30% 51.35 kB 51.50 kB +0.24% 14.39 kB 14.43 kB
facebook-www/ReactDOM-prod.modern.js +0.28% 401.13 kB 402.24 kB +0.21% 74.56 kB 74.71 kB
facebook-www/ReactDOMForked-prod.modern.js +0.28% 401.13 kB 402.24 kB +0.21% 74.56 kB 74.72 kB
facebook-www/ReactDOM-prod.classic.js +0.27% 412.54 kB 413.66 kB +0.22% 76.28 kB 76.44 kB
facebook-www/ReactDOMForked-prod.classic.js +0.27% 412.54 kB 413.66 kB +0.22% 76.28 kB 76.45 kB
facebook-www/ReactDOM-profiling.modern.js +0.26% 428.75 kB 429.87 kB +0.21% 79.25 kB 79.42 kB
facebook-www/ReactDOMForked-profiling.modern.js +0.26% 428.75 kB 429.87 kB +0.21% 79.25 kB 79.42 kB
facebook-www/ReactDOM-profiling.classic.js +0.25% 440.22 kB 441.34 kB +0.19% 80.93 kB 81.09 kB
facebook-www/ReactDOMForked-profiling.classic.js +0.25% 440.22 kB 441.34 kB +0.19% 80.94 kB 81.09 kB

Generated by 🚫 dangerJS against c10215e

@salazarm salazarm changed the title Sync hydrate capture and dont replay discrete events Sync hydrate discrete events in capture phase and dont replay discrete events Sep 28, 2021
@salazarm
Copy link
Contributor Author

Ping @gaearon @sebastianopeluso . Basically just deleted the discrete event replaying and moved the synchronous hydration block from queueDiscreteEvent into the event listener.

Copy link
Member

@rickhanlonii rickhanlonii left a comment

Choose a reason for hiding this comment

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

This looks great! Left some comments, and also think we should consider running this change in an experiment. Will followup offline 👍

@salazarm
Copy link
Contributor Author

salazarm commented Oct 1, 2021

Gated the changes so we can test it. Gated some of the tests too but for some I checked the flag within the test since I didn't want to invalidate the whole test when the flag was on

Copy link
Collaborator

@gaearon gaearon left a comment

Choose a reason for hiding this comment

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

let's try it, but please fix up reduced coverage and skip unrelated changes before merging

@salazarm salazarm changed the base branch from main to 0.3-stable October 4, 2021 13:35
@salazarm salazarm changed the base branch from 0.3-stable to main October 4, 2021 13:35
@salazarm salazarm merged commit 0ecbbe1 into facebook:main Oct 4, 2021
@salazarm salazarm deleted the syncHydrateCaptureAndDontReplayDiscreteEvents branch October 4, 2021 13:54
@gaearon
Copy link
Collaborator

gaearon commented Oct 4, 2021

What happens if we're not ready to hydrate? Do we prevent the event propagation in the capture phase? I think we planned to do this but I'm not sure if that's still the plan, and whether this is how it works in this PR.

@salazarm
Copy link
Contributor Author

salazarm commented Oct 4, 2021

What happens if we're not ready to hydrate? Do we prevent the event propagation in the capture phase? I think we planned to do this but I'm not sure if that's still the plan, and whether this is how it works in this PR.

Yeah thats a mistake on my part I should have added that in the PR too but it felt like we were less confident about that part of the change. Here are the notes from when we spoke about it:

  • Use capture phase on root to attempt to sync hydrate, if it fails calls stopPropagation. Ideally, it would allow it to bubble, but we’ll try without it first. You can’t re-dispatch because it would re-fire the capture phase. The downside is that things above will not be bubble, so either they’ll miss events, or they’ll only see the full phase before react starts hydrating.
    * Another option is to only disable React events but we want to get away from React events being special
    * This also has the preventDefault issue, where react events contain preventDefaults, that would prevent the native events from firing if we were actually hydrated

So It sounds like we wanted to test:

  1. Calling stopPropagation if hydration fails
  2. Allowing it to bubble if hydration fails

This current PR does (2) but I can put up a follow up PR to test (1) first since thats what we agreed to

@gaearon
Copy link
Collaborator

gaearon commented Oct 4, 2021

I'm a bit confused by what you mean by "to test". I'm speaking more about the behavior itself, not only the tests. What is the current behavior, and what is the behavior that was agreed to? I wasn't actively tracking the discussion so maybe it would be good if you could confirm with Seb what the latest decision was. Either it should stop propagation during unhydrated capture, or it should not.

@sebmarkbage
Copy link
Collaborator

sebmarkbage commented Oct 4, 2021

I'm a bit confused by what you mean by "to test".

I think he meant "to test" as in "to try". I think that's the decision. To see what happens in practice.

I think we'll want to try (1) instead though.

Allowing it to continue means that it doesn't just bubble up the parent but also down the children. I think we'll want to add a test that the parent listener doesn't ever fire in this case:

let triggeredParent = false;
...
<div onClick={() => triggeredParent = true}>
  <Suspense fallback={null}>
    <div onClick={e => e.stopPropagation()} ref={ref} />
  </Suspense>
</div>
...
ref.current.dispatchEvent(...);
...
expect(triggeredParent).toBe(false);

These days, so many apps use React all the way to the <body> - the impact of what happens outside the React root is much more scoped. It seems best to at least preserve consistent the semantics within the root which would be most adhoc stuff.

@gaearon
Copy link
Collaborator

gaearon commented Oct 4, 2021

I think he meant "to test" as in "to try".

Ahh ok I get it now. 😛

So the conclusion is let's do a follow-up for (1) with the test you described before enabling the flag.

@salazarm
Copy link
Contributor Author

salazarm commented Oct 4, 2021

Interestingly I wrote the test but it already passes without any other changes🤔

@salazarm
Copy link
Contributor Author

salazarm commented Oct 4, 2021

#22502

Copy link
Collaborator

@sebmarkbage sebmarkbage left a comment

Choose a reason for hiding this comment

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

Let's try rolling this out quickly and then move on to cleaning up the code. Because the code structure now is way over-engineered for what it's actually doing and keeping this structure will just lead to confusion and dogma about keeping the structure.

I'd like to see what this looks like with all the unnecessary code removed and the attempt refactored.

Also, the goal is to eventually get rid of the synthetic event system. This dispatch function only exists for that purposes.

To explain this layering it would be good if that all the sync hydration stuff was the first thing that happened in the listener without early bailout - or even it's own listener. That way we know that the system behaves the same with React events as with other event listeners.

For example the whole attemptToDispatchEvent and blockedOn structure probably doesn't make sense anymore.

facebook-github-bot pushed a commit to facebook/react-native that referenced this pull request Oct 12, 2021
Summary:
This sync includes the following changes:
- **[579c008a7](facebook/react@579c008a7 )**: [Fizz/Flight] pipeToNodeWritable(..., writable).startWriting() -> renderToPipeableStream(...).pipe(writable) ([#22450](facebook/react#22450)) //<Sebastian Markbåge>//
- **[f2c381131](facebook/react@f2c381131 )**: fix: useSyncExternalStoreExtra ([#22500](facebook/react#22500)) //<Daishi Kato>//
- **[0ecbbe142](facebook/react@0ecbbe142 )**: Sync hydrate discrete events in capture phase and dont replay discrete events ([#22448](facebook/react#22448)) //<salazarm>//
- **[a724a3b57](facebook/react@a724a3b57 )**: [RFC] Codemod invariant -> throw new Error ([#22435](facebook/react#22435)) //<Andrew Clark>//
- **[201af81b0](facebook/react@201af81b0 )**: Release pooled cache reference in complete/unwind ([#22464](facebook/react#22464)) //<Joseph Savona>//
- **[033efe731](facebook/react@033efe731 )**: Call get snapshot in useSyncExternalStore server shim ([#22453](facebook/react#22453)) //<salazarm>//
- **[7843b142a](facebook/react@7843b142a )**: [Fizz/Flight] Pass in Destination lazily to startFlowing instead of in createRequest ([#22449](facebook/react#22449)) //<Sebastian Markbåge>//
- **[d9fb383d6](facebook/react@d9fb383d6 )**: Extract queueing logic into shared functions ([#22452](facebook/react#22452)) //<Andrew Clark>//
- **[9175f4d15](facebook/react@9175f4d15 )**: Scheduling Profiler: Show Suspense resource .displayName ([#22451](facebook/react#22451)) //<Brian Vaughn>//
- **[eba248c39](facebook/react@eba248c39 )**: [Fizz/Flight] Remove reentrancy hack ([#22446](facebook/react#22446)) //<Sebastian Markbåge>//
- **[66388150e](facebook/react@66388150e )**: Remove usereducer eager bailout ([#22445](facebook/react#22445)) //<Joseph Savona>//
- **[d3e086932](facebook/react@d3e086932 )**: Make root.unmount() synchronous  ([#22444](facebook/react#22444)) //<Andrew Clark>//
- **[2cc6d79c9](facebook/react@2cc6d79c9 )**: Rename onReadyToStream to onCompleteShell ([#22443](facebook/react#22443)) //<Sebastian Markbåge>//
- **[c88fb49d3](facebook/react@c88fb49d3 )**: Improve DEV errors if string coercion throws (Temporal.*, Symbol, etc.) ([#22064](facebook/react#22064)) //<Justin Grant>//
- **[05726d72c](facebook/react@05726d72c )**: [Fix] Errors should not "unsuspend" a transition ([#22423](facebook/react#22423)) //<Andrew Clark>//
- **[3746eaf98](facebook/react@3746eaf98 )**: Packages/React/src/ReactLazy ---> changing -1 to unintialized ([#22421](facebook/react#22421)) //<BIKI DAS>//
- **[04ccc01d9](facebook/react@04ccc01d9 )**: Hydration errors should force a client render ([#22416](facebook/react#22416)) //<Andrew Clark>//
- **[029fdcebb](facebook/react@029fdcebb )**: root.hydrate -> root.isDehydrated ([#22420](facebook/react#22420)) //<Andrew Clark>//
- **[af87f5a83](facebook/react@af87f5a83 )**: Scheduling Profiler marks should include thrown Errors ([#22417](facebook/react#22417)) //<Brian Vaughn>//
- **[d47339ea3](facebook/react@d47339ea3 )**: [Fizz] Remove assignID mechanism ([#22410](facebook/react#22410)) //<Sebastian Markbåge>//
- **[3a50d9557](facebook/react@3a50d9557 )**: Never attach ping listeners in legacy Suspense ([#22407](facebook/react#22407)) //<Andrew Clark>//
- **[82c8fa90b](facebook/react@82c8fa90b )**: Add back useMutableSource temporarily ([#22396](facebook/react#22396)) //<Andrew Clark>//
- **[5b57bc6e3](facebook/react@5b57bc6e3 )**: [Draft] don't patch console during first render ([#22308](facebook/react#22308)) //<Luna Ruan>//
- **[cf07c3df1](facebook/react@cf07c3df1 )**: Delete all but one `build2` reference ([#22391](facebook/react#22391)) //<Andrew Clark>//
- **[bb0d06935](facebook/react@bb0d06935 )**: [build2 -> build] Local scripts //<Andrew Clark>//
- **[0c81d347b](facebook/react@0c81d347b )**: Write artifacts to `build` instead of `build2` //<Andrew Clark>//
- **[4da03c9fb](facebook/react@4da03c9fb )**: useSyncExternalStore React Native version ([#22367](facebook/react#22367)) //<salazarm>//
- **[48d475c9e](facebook/react@48d475c9e )**: correct typos ([#22294](facebook/react#22294)) //<Bowen>//
- **[cb6c619c0](facebook/react@cb6c619c0 )**: Remove Fiber fields that were used for hydrating useMutableSource ([#22368](facebook/react#22368)) //<Sebastian Silbermann>//
- **[64e70f82e](facebook/react@64e70f82e )**: [Fizz] add avoidThisFallback support ([#22318](facebook/react#22318)) //<salazarm>//
- **[3ee7a004e](facebook/react@3ee7a004e )**: devtools: Display actual ReactDOM API name in root type ([#22363](facebook/react#22363)) //<Sebastian Silbermann>//
- **[79b8fc667](facebook/react@79b8fc667 )**: Implement getServerSnapshot in userspace shim ([#22359](facebook/react#22359)) //<Andrew Clark>//
- **[86b3e2461](facebook/react@86b3e2461 )**: Implement useSyncExternalStore on server ([#22347](facebook/react#22347)) //<Andrew Clark>//
- **[8209de269](facebook/react@8209de269 )**: Delete useMutableSource implementation ([#22292](facebook/react#22292)) //<Andrew Clark>//

Changelog:
[General][Changed] - React Native sync for revisions e8feb11...afcb9cd

jest_e2e[run_all_tests]

Reviewed By: yungsters

Differential Revision: D31541359

fbshipit-source-id: c35941bc303fdf55cb061e9996200dc868a6f2af
@gaearon gaearon mentioned this pull request Mar 29, 2022
zhengjitf pushed a commit to zhengjitf/react that referenced this pull request Apr 15, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
CLA Signed React Core Team Opened by a member of the React Core Team
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants