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

Switch to client rendering if root receives update #23309

Merged
merged 1 commit into from
Feb 16, 2022

Conversation

acdlite
Copy link
Collaborator

@acdlite acdlite commented Feb 16, 2022

If a hydration root receives an update before the outermost shell has finished hydrating, we should give up hydrating and switch to client rendering.

Since the shell is expected to commit quickly, this doesn't happen that often. The most common sequence is something in the shell suspends, and then the user quickly navigates to a different screen, triggering a top-level update.

Instead of immediately switching to client rendering, we could first attempt to hydration at higher priority, like we do for updates that occur inside nested dehydrated trees.

But since this case is expected to be rare, and mainly only happens when the shell is suspended, an attempt at higher priority would likely end up suspending again anyway, so it would be wasted effort. Implementing it this way would also require us to add a new lane especially for root hydration. For simplicity's sake, we'll immediately switch to client rendering. In the future, if we find another use case for a root hydration lane, we'll reconsider.

@acdlite acdlite requested a review from sebmarkbage February 16, 2022 06:03
@facebook-github-bot facebook-github-bot added the React Core Team Opened by a member of the React Core Team label Feb 16, 2022
@@ -66,20 +66,21 @@ export function act(scope: () => Thenable<mixed> | void) {
// returned and 2) we could use async/await. Since it's only our used in
// our test suite, we should be able to.
try {
const thenable = scope();
const result = scope();
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I updated our internal implementation of act to resolve to the return value of the scope function, like how the public implementation of act works. Not relevant to the rest of the PR, just wanted it for a test.

@acdlite acdlite changed the title Root update during hydration Switch to client rendering if root receives update Feb 16, 2022
@sizebot
Copy link

sizebot commented Feb 16, 2022

Comparing: f7f7ed0...3b122d5

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 +0.15% 130.59 kB 130.78 kB +0.15% 41.85 kB 41.92 kB
oss-experimental/react-dom/cjs/react-dom.production.min.js +0.14% 135.57 kB 135.76 kB +0.17% 43.33 kB 43.40 kB
facebook-www/ReactDOM-prod.classic.js +0.13% 431.62 kB 432.17 kB +0.11% 79.20 kB 79.29 kB
facebook-www/ReactDOM-prod.modern.js +0.14% 421.50 kB 422.09 kB +0.13% 77.73 kB 77.83 kB
facebook-www/ReactDOMForked-prod.classic.js +0.13% 431.62 kB 432.17 kB +0.11% 79.21 kB 79.30 kB

Significant size changes

Includes any change greater than 0.2%:

Expand to show
Name +/- Base Current +/- gzip Base gzip Current gzip
oss-experimental/jest-react/cjs/jest-react.development.js +1.82% 11.41 kB 11.61 kB +1.06% 3.87 kB 3.91 kB
oss-stable-semver/jest-react/cjs/jest-react.development.js +1.82% 11.41 kB 11.61 kB +1.06% 3.87 kB 3.91 kB
oss-stable/jest-react/cjs/jest-react.development.js +1.82% 11.41 kB 11.61 kB +1.06% 3.87 kB 3.91 kB
oss-experimental/jest-react/cjs/jest-react.production.min.js +1.42% 2.39 kB 2.43 kB +0.77% 1.17 kB 1.18 kB
oss-stable-semver/jest-react/cjs/jest-react.production.min.js +1.42% 2.39 kB 2.43 kB +0.77% 1.17 kB 1.18 kB
oss-stable/jest-react/cjs/jest-react.production.min.js +1.42% 2.39 kB 2.43 kB +0.77% 1.17 kB 1.18 kB
oss-stable-semver/react-reconciler/cjs/react-reconciler.development.js +0.45% 732.70 kB 735.98 kB +0.54% 156.08 kB 156.92 kB
oss-stable/react-reconciler/cjs/react-reconciler.development.js +0.45% 732.70 kB 735.98 kB +0.54% 156.08 kB 156.92 kB
oss-experimental/react-reconciler/cjs/react-reconciler.development.js +0.43% 758.67 kB 761.95 kB +0.51% 161.47 kB 162.29 kB
oss-stable-semver/react-reconciler/cjs/react-reconciler.production.min.js +0.38% 92.44 kB 92.79 kB +0.36% 28.49 kB 28.59 kB
oss-stable/react-reconciler/cjs/react-reconciler.production.min.js +0.38% 92.44 kB 92.79 kB +0.36% 28.49 kB 28.59 kB
oss-experimental/react-reconciler/cjs/react-reconciler.production.min.js +0.36% 96.85 kB 97.20 kB +0.31% 29.67 kB 29.76 kB
oss-stable-semver/react-reconciler/cjs/react-reconciler.profiling.min.js +0.35% 101.35 kB 101.70 kB +0.23% 30.70 kB 30.77 kB
oss-stable/react-reconciler/cjs/react-reconciler.profiling.min.js +0.35% 101.35 kB 101.70 kB +0.23% 30.70 kB 30.77 kB
oss-experimental/react-reconciler/cjs/react-reconciler.profiling.min.js +0.33% 105.75 kB 106.10 kB +0.23% 32.00 kB 32.07 kB
oss-experimental/react-dom/cjs/react-dom-unstable_testing.development.js +0.32% 993.24 kB 996.38 kB +0.35% 225.03 kB 225.81 kB
facebook-www/ReactDOMTesting-dev.modern.js +0.32% 1,015.35 kB 1,018.56 kB +0.35% 228.82 kB 229.61 kB
oss-stable-semver/react-dom/cjs/react-dom.development.js +0.31% 1,005.03 kB 1,008.18 kB +0.36% 225.70 kB 226.50 kB
oss-stable/react-dom/cjs/react-dom.development.js +0.31% 1,005.03 kB 1,008.18 kB +0.36% 225.70 kB 226.50 kB
oss-stable-semver/react-dom/umd/react-dom.development.js +0.31% 1,054.88 kB 1,058.14 kB +0.35% 228.16 kB 228.96 kB
oss-stable/react-dom/umd/react-dom.development.js +0.31% 1,054.88 kB 1,058.14 kB +0.35% 228.16 kB 228.96 kB
facebook-www/ReactDOMTesting-dev.classic.js +0.31% 1,042.31 kB 1,045.52 kB +0.33% 234.26 kB 235.05 kB
oss-experimental/react-dom/cjs/react-dom.development.js +0.30% 1,033.45 kB 1,036.60 kB +0.34% 231.54 kB 232.33 kB
oss-experimental/react-dom/umd/react-dom.development.js +0.30% 1,084.68 kB 1,087.93 kB +0.33% 234.13 kB 234.90 kB
facebook-www/ReactDOMForked-dev.modern.js +0.28% 1,108.55 kB 1,111.70 kB +0.32% 245.30 kB 246.08 kB
facebook-www/ReactDOM-dev.modern.js +0.28% 1,108.55 kB 1,111.70 kB +0.32% 245.30 kB 246.08 kB
facebook-www/ReactDOMForked-dev.classic.js +0.28% 1,130.87 kB 1,134.02 kB +0.30% 249.51 kB 250.26 kB
facebook-www/ReactDOM-dev.classic.js +0.28% 1,130.87 kB 1,134.02 kB +0.30% 249.51 kB 250.26 kB
oss-stable-semver/react-test-renderer/cjs/react-test-renderer.development.js +0.21% 617.39 kB 618.69 kB +0.32% 135.67 kB 136.11 kB
oss-stable/react-test-renderer/cjs/react-test-renderer.development.js +0.21% 617.39 kB 618.69 kB +0.32% 135.67 kB 136.11 kB
oss-stable-semver/react-test-renderer/umd/react-test-renderer.development.js +0.21% 647.23 kB 648.58 kB +0.28% 137.13 kB 137.52 kB
oss-stable/react-test-renderer/umd/react-test-renderer.development.js +0.21% 647.23 kB 648.58 kB +0.28% 137.13 kB 137.52 kB
facebook-react-native/react-test-renderer/cjs/ReactTestRenderer-dev.js +0.21% 629.91 kB 631.21 kB +0.31% 136.86 kB 137.29 kB
oss-experimental/react-test-renderer/cjs/react-test-renderer.development.js +0.20% 643.18 kB 644.48 kB +0.29% 141.06 kB 141.47 kB
oss-experimental/react-test-renderer/umd/react-test-renderer.development.js +0.20% 674.30 kB 675.65 kB +0.28% 142.54 kB 142.93 kB

Generated by 🚫 dangerJS against 3b122d5

@acdlite acdlite force-pushed the root-update-during-hydration branch from 7e2f049 to 579dfbb Compare February 16, 2022 06:36

// @gate !enableSyncDefaultUpdates
// @gate enableUseMutableSource
it('should detect a tear during a higher priority interruption', () => {
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I deleted this test instead of rewriting it since useMutableSource is slated for removal anyway

@acdlite acdlite force-pushed the root-update-during-hydration branch from 579dfbb to ca8f39b Compare February 16, 2022 06:40
@@ -244,6 +245,8 @@ function findHostInstanceWithWarning(
export function createContainer(
containerInfo: Container,
tag: RootTag,
// TODO: We can remove hydration-specific stuff from createContainer once
// we delete legacy mode. The new root API uses createDehydratedContainer.
Copy link
Contributor

@salazarm salazarm Feb 16, 2022

Choose a reason for hiding this comment

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

typo? You wrote createDehydratedContainer instead of createHydrationContainer ?

If a hydration root receives an update before the outermost shell has
finished hydrating, we should give up hydrating and switch to
client rendering.

Since the shell is expected to commit quickly, this doesn't happen that
often. The most common sequence is something in the shell suspends, and
then the user quickly navigates to a different screen, triggering a
top-level update.

Instead of immediately switching to client rendering, we could first
attempt to hydration at higher priority, like we do for updates that
occur inside nested dehydrated trees.

But since this case is expected to be rare, and mainly only happens when
the shell is suspended, an attempt at higher priority would likely end
up suspending again anyway, so it would be wasted effort. Implementing
it this way would also require us to add a new lane especially for root
hydration. For simplicity's sake, we'll immediately switch to client
rendering. In the future, if we find another use case for a root
hydration lane, we'll reconsider.
@acdlite acdlite force-pushed the root-update-during-hydration branch from ca8f39b to 3b122d5 Compare February 16, 2022 17:51
@acdlite acdlite merged commit 80059bb into facebook:main Feb 16, 2022
facebook-github-bot pushed a commit to facebook/react-native that referenced this pull request Feb 24, 2022
Summary:
This sync includes the following changes:
- **[4de99b3ca](facebook/react@4de99b3ca )**: fix getSnapshot warning when a selector returns NaN ([#23333](facebook/react#23333)) //<OGURA Daiki>//
- **[40eaa22d9](facebook/react@40eaa22d9 )**: Remove dependency on Offscreen Fiber updateQueue for React Cache ([#23229](facebook/react#23229)) //<Luna Ruan>//
- **[caf6d4707](facebook/react@caf6d4707 )**: Enable enableCache on Test Renderer native ([#23314](facebook/react#23314)) //<David McCabe>//
- **[419ccc2b1](facebook/react@419ccc2b1 )**: Land skipUnmountedBoundaries experiment ([#23322](facebook/react#23322)) //<Andrew Clark>//
- **[54f785bc5](facebook/react@54f785bc5 )**: Disallow comments as DOM containers for createRoot ([#23321](facebook/react#23321)) //<Andrew Clark>//
- **[e9aa9592c](facebook/react@e9aa9592c )**: change ReactBatchConfig.transition //<Luna Ruan>//
- **[51c8411d9](facebook/react@51c8411d9 )**: Log a recoverable error whenever hydration fails ([#23319](facebook/react#23319)) //<Andrew Clark>//
- **[79ed5e18f](facebook/react@79ed5e18f )**: Delete vestigial RetryAfterError logic ([#23312](facebook/react#23312)) //<Andrew Clark>//
- **[80059bb73](facebook/react@80059bb73 )**: Switch to client rendering if root receives update ([#23309](facebook/react#23309)) //<Andrew Clark>//
- **[f7f7ed089](facebook/react@f7f7ed089 )**: Allow suspending in the shell during hydration ([#23304](facebook/react#23304)) //<Andrew Clark>//

Changelog:
[General][Changed] - React Native sync for revisions 27b5699...4de99b3

jest_e2e[run_all_tests]

Reviewed By: rickhanlonii

Differential Revision: D34399162

fbshipit-source-id: 5c49e2bdcf63eb6a601cfa6a4e4b8f2e1f83e2dd
acdlite added a commit to acdlite/react that referenced this pull request Mar 12, 2022
I already made this change for the concurrent root API in facebook#23309. This
does the same thing for the legacy API.

Doesn't change any behavior, but I will use this in the next steps.
acdlite added a commit to acdlite/react that referenced this pull request Mar 12, 2022
I already made this change for the concurrent root API in facebook#23309. This
does the same thing for the legacy API.

Doesn't change any behavior, but I will use this in the next steps.
acdlite added a commit to acdlite/react that referenced this pull request Mar 12, 2022
I already made this change for the concurrent root API in facebook#23309. This
does the same thing for the legacy API.

Doesn't change any behavior, but I will use this in the next steps.
acdlite added a commit to acdlite/react that referenced this pull request Mar 20, 2022
I already made this change for the concurrent root API in facebook#23309. This
does the same thing for the legacy API.

Doesn't change any behavior, but I will use this in the next steps.
acdlite added a commit that referenced this pull request Mar 20, 2022
…nt render (#24082)

* Pass children to hydration root constructor

I already made this change for the concurrent root API in #23309. This
does the same thing for the legacy API.

Doesn't change any behavior, but I will use this in the next steps.

* Add isRootDehydrated function

Currently this does nothing except read a boolean field, but I'm about
to change this logic.

Since this is accessed by React DOM, too, I put the function in a
separate module that can be deep imported. Previously, it was accessing
the FiberRoot directly. The reason it's a separate module is to break a
circular dependency between React DOM and the reconciler.

* Allow updates at lower pri without forcing client render

Currently, if a root is updated before the shell has finished hydrating
(for example, due to a top-level navigation), we immediately revert to
client rendering. This is rare because the root is expected is finish
quickly, but not exceedingly rare because the root may be suspended.

This adds support for updating the root without forcing a client render
as long as the update has lower priority than the initial hydration,
i.e. if the update is wrapped in startTransition.

To implement this, I had to do some refactoring. The main idea here is
to make it closer to how we implement hydration in Suspense boundaries:

- I moved isDehydrated from the shared FiberRoot object to the
HostRoot's state object.
- In the begin phase, I check if the root has received an by comparing
the new children to the initial children. If they are different, we
revert to client rendering, and set isDehydrated to false using a
derived state update (a la getDerivedStateFromProps).
- There are a few places where we used to set root.isDehydrated to false
as a way to force a client render. Instead, I set the ForceClientRender
flag on the root work-in-progress fiber.
- Whenever we fall back to client rendering, I log a recoverable error.

The overall code structure is almost identical to the corresponding
logic for Suspense components.

The reason this works is because if the update has lower priority than
the initial hydration, it won't be processed during the hydration
render, so the children will be the same.

We can go even further and allow updates at _higher_ priority (though
not sync) by implementing selective hydration at the root, like we do
for Suspense boundaries: interrupt the current render, attempt hydration
at slightly higher priority than the update, then continue rendering the
update. I haven't implemented this yet, but I've structured the code in
anticipation of adding this later.

* Wrap useMutableSource logic in feature flag
zhengjitf pushed a commit to zhengjitf/react that referenced this pull request Apr 15, 2022
If a hydration root receives an update before the outermost shell has
finished hydrating, we should give up hydrating and switch to
client rendering.

Since the shell is expected to commit quickly, this doesn't happen that
often. The most common sequence is something in the shell suspends, and
then the user quickly navigates to a different screen, triggering a
top-level update.

Instead of immediately switching to client rendering, we could first
attempt to hydration at higher priority, like we do for updates that
occur inside nested dehydrated trees.

But since this case is expected to be rare, and mainly only happens when
the shell is suspended, an attempt at higher priority would likely end
up suspending again anyway, so it would be wasted effort. Implementing
it this way would also require us to add a new lane especially for root
hydration. For simplicity's sake, we'll immediately switch to client
rendering. In the future, if we find another use case for a root
hydration lane, we'll reconsider.
zhengjitf pushed a commit to zhengjitf/react that referenced this pull request Apr 15, 2022
I already made this change for the concurrent root API in facebook#23309. This
does the same thing for the legacy API.

Doesn't change any behavior, but I will use this in the next steps.
zhengjitf pushed a commit to zhengjitf/react that referenced this pull request Apr 15, 2022
…nt render (facebook#24082)

* Pass children to hydration root constructor

I already made this change for the concurrent root API in facebook#23309. This
does the same thing for the legacy API.

Doesn't change any behavior, but I will use this in the next steps.

* Add isRootDehydrated function

Currently this does nothing except read a boolean field, but I'm about
to change this logic.

Since this is accessed by React DOM, too, I put the function in a
separate module that can be deep imported. Previously, it was accessing
the FiberRoot directly. The reason it's a separate module is to break a
circular dependency between React DOM and the reconciler.

* Allow updates at lower pri without forcing client render

Currently, if a root is updated before the shell has finished hydrating
(for example, due to a top-level navigation), we immediately revert to
client rendering. This is rare because the root is expected is finish
quickly, but not exceedingly rare because the root may be suspended.

This adds support for updating the root without forcing a client render
as long as the update has lower priority than the initial hydration,
i.e. if the update is wrapped in startTransition.

To implement this, I had to do some refactoring. The main idea here is
to make it closer to how we implement hydration in Suspense boundaries:

- I moved isDehydrated from the shared FiberRoot object to the
HostRoot's state object.
- In the begin phase, I check if the root has received an by comparing
the new children to the initial children. If they are different, we
revert to client rendering, and set isDehydrated to false using a
derived state update (a la getDerivedStateFromProps).
- There are a few places where we used to set root.isDehydrated to false
as a way to force a client render. Instead, I set the ForceClientRender
flag on the root work-in-progress fiber.
- Whenever we fall back to client rendering, I log a recoverable error.

The overall code structure is almost identical to the corresponding
logic for Suspense components.

The reason this works is because if the update has lower priority than
the initial hydration, it won't be processed during the hydration
render, so the children will be the same.

We can go even further and allow updates at _higher_ priority (though
not sync) by implementing selective hydration at the root, like we do
for Suspense boundaries: interrupt the current render, attempt hydration
at slightly higher priority than the update, then continue rendering the
update. I haven't implemented this yet, but I've structured the code in
anticipation of adding this later.

* Wrap useMutableSource logic in feature flag
Saadnajmi pushed a commit to Saadnajmi/react-native-macos that referenced this pull request Jan 15, 2023
Summary:
This sync includes the following changes:
- **[4de99b3ca](facebook/react@4de99b3ca )**: fix getSnapshot warning when a selector returns NaN ([facebook#23333](facebook/react#23333)) //<OGURA Daiki>//
- **[40eaa22d9](facebook/react@40eaa22d9 )**: Remove dependency on Offscreen Fiber updateQueue for React Cache ([facebook#23229](facebook/react#23229)) //<Luna Ruan>//
- **[caf6d4707](facebook/react@caf6d4707 )**: Enable enableCache on Test Renderer native ([facebook#23314](facebook/react#23314)) //<David McCabe>//
- **[419ccc2b1](facebook/react@419ccc2b1 )**: Land skipUnmountedBoundaries experiment ([facebook#23322](facebook/react#23322)) //<Andrew Clark>//
- **[54f785bc5](facebook/react@54f785bc5 )**: Disallow comments as DOM containers for createRoot ([facebook#23321](facebook/react#23321)) //<Andrew Clark>//
- **[e9aa9592c](facebook/react@e9aa9592c )**: change ReactBatchConfig.transition //<Luna Ruan>//
- **[51c8411d9](facebook/react@51c8411d9 )**: Log a recoverable error whenever hydration fails ([facebook#23319](facebook/react#23319)) //<Andrew Clark>//
- **[79ed5e18f](facebook/react@79ed5e18f )**: Delete vestigial RetryAfterError logic ([facebook#23312](facebook/react#23312)) //<Andrew Clark>//
- **[80059bb73](facebook/react@80059bb73 )**: Switch to client rendering if root receives update ([facebook#23309](facebook/react#23309)) //<Andrew Clark>//
- **[f7f7ed089](facebook/react@f7f7ed089 )**: Allow suspending in the shell during hydration ([facebook#23304](facebook/react#23304)) //<Andrew Clark>//

Changelog:
[General][Changed] - React Native sync for revisions 27b5699...4de99b3

jest_e2e[run_all_tests]

Reviewed By: rickhanlonii

Differential Revision: D34399162

fbshipit-source-id: 5c49e2bdcf63eb6a601cfa6a4e4b8f2e1f83e2dd
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.

4 participants