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

useDeferredValue should skip initialValue if it suspends #27509

Merged
merged 2 commits into from
Oct 17, 2023

Conversation

acdlite
Copy link
Collaborator

@acdlite acdlite commented Oct 12, 2023

Based on #27505

If a parent render spawns a deferred task with useDeferredValue, but the parent render suspends, we should not wait for the parent render to complete before attempting to render the final value.

The reason is that the initialValue argument to useDeferredValue is meant to represent an immediate preview of the final UI. If we can't render it "immediately", we might as well skip it and go straight to the "real" value.

This is an improvement over how a userspace implementation of useDeferredValue would work, because a userspace implementation would have to wait for the parent task to commit (useEffect) before spawning the deferred task, creating a waterfall.

@facebook-github-bot facebook-github-bot added CLA Signed React Core Team Opened by a member of the React Core Team labels Oct 12, 2023
@react-sizebot
Copy link

react-sizebot commented Oct 12, 2023

Comparing: 9abf6fa...66267d0

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.13% 174.68 kB 174.91 kB +0.22% 54.32 kB 54.43 kB
oss-experimental/react-dom/cjs/react-dom.production.min.js +0.16% 176.77 kB 177.05 kB +0.27% 54.99 kB 55.13 kB
facebook-www/ReactDOM-prod.classic.js +0.27% 565.55 kB 567.06 kB +0.28% 99.53 kB 99.81 kB
facebook-www/ReactDOM-prod.modern.js +0.28% 549.40 kB 550.92 kB +0.30% 96.60 kB 96.90 kB

Significant size changes

Includes any change greater than 0.2%:

Expand to show
Name +/- Base Current +/- gzip Base gzip Current gzip
react-native/implementations/ReactFabric-prod.js +0.48% 319.64 kB 321.16 kB +0.55% 56.35 kB 56.67 kB
react-native/implementations/ReactFabric-prod.fb.js +0.46% 327.94 kB 329.46 kB +0.55% 58.08 kB 58.40 kB
react-native/implementations/ReactNativeRenderer-prod.js +0.46% 328.62 kB 330.14 kB +0.50% 58.09 kB 58.38 kB
facebook-react-native/react-test-renderer/cjs/ReactTestRenderer-prod.js +0.46% 308.83 kB 310.24 kB +0.47% 54.60 kB 54.86 kB
react-native/implementations/ReactNativeRenderer-prod.fb.js +0.45% 335.23 kB 336.76 kB +0.49% 59.49 kB 59.78 kB
react-native/implementations/ReactFabric-profiling.js +0.45% 339.21 kB 340.74 kB +0.51% 59.56 kB 59.86 kB
facebook-www/ReactART-prod.modern.js +0.45% 336.37 kB 337.88 kB +0.52% 57.09 kB 57.38 kB
react-native/implementations/ReactFabric-profiling.fb.js +0.44% 355.34 kB 356.91 kB +0.50% 62.30 kB 62.61 kB
react-native/implementations/ReactNativeRenderer-profiling.js +0.44% 348.20 kB 349.73 kB +0.47% 61.32 kB 61.61 kB
react-native/implementations/ReactNativeRenderer-profiling.fb.js +0.43% 362.67 kB 364.24 kB +0.48% 63.75 kB 64.06 kB
facebook-react-native/react-test-renderer/cjs/ReactTestRenderer-profiling.js +0.43% 324.71 kB 326.12 kB +0.49% 57.03 kB 57.30 kB
facebook-www/ReactART-prod.classic.js +0.43% 347.23 kB 348.74 kB +0.47% 58.99 kB 59.27 kB
facebook-www/ReactTestRenderer-dev.modern.js +0.34% 835.87 kB 838.74 kB +0.38% 181.18 kB 181.87 kB
facebook-www/ReactTestRenderer-dev.classic.js +0.34% 835.87 kB 838.74 kB +0.38% 181.18 kB 181.87 kB
facebook-react-native/react-test-renderer/cjs/ReactTestRenderer-dev.js +0.33% 828.15 kB 830.91 kB +0.35% 180.08 kB 180.72 kB
react-native/implementations/ReactFabric-dev.js +0.33% 868.69 kB 871.56 kB +0.37% 189.51 kB 190.21 kB
oss-experimental/react-test-renderer/cjs/react-test-renderer.development.js +0.33% 817.02 kB 819.72 kB +0.36% 178.89 kB 179.54 kB
react-native/implementations/ReactFabric-dev.fb.js +0.33% 896.78 kB 899.71 kB +0.36% 194.57 kB 195.28 kB
oss-experimental/react-art/cjs/react-art.development.js +0.33% 843.73 kB 846.49 kB +0.36% 184.04 kB 184.70 kB
oss-experimental/react-test-renderer/umd/react-test-renderer.development.js +0.33% 855.55 kB 858.34 kB +0.39% 180.72 kB 181.42 kB
react-native/implementations/ReactNativeRenderer-dev.js +0.33% 883.18 kB 886.05 kB +0.37% 193.72 kB 194.44 kB
react-native/implementations/ReactNativeRenderer-dev.fb.js +0.32% 909.75 kB 912.69 kB +0.37% 198.41 kB 199.14 kB
facebook-www/ReactART-dev.modern.js +0.32% 911.69 kB 914.63 kB +0.36% 194.94 kB 195.64 kB
oss-stable-semver/react-art/cjs/react-art.development.js +0.32% 833.75 kB 836.42 kB +0.34% 182.20 kB 182.82 kB
oss-stable/react-art/cjs/react-art.development.js +0.32% 833.78 kB 836.44 kB +0.35% 182.22 kB 182.85 kB
oss-stable-semver/react-test-renderer/cjs/react-test-renderer.development.js +0.32% 815.63 kB 818.23 kB +0.34% 178.62 kB 179.23 kB
oss-stable/react-test-renderer/cjs/react-test-renderer.development.js +0.32% 815.66 kB 818.26 kB +0.34% 178.65 kB 179.27 kB
facebook-www/ReactART-dev.classic.js +0.32% 922.90 kB 925.83 kB +0.36% 197.23 kB 197.95 kB
oss-stable-semver/react-test-renderer/umd/react-test-renderer.development.js +0.31% 854.12 kB 856.80 kB +0.36% 180.47 kB 181.12 kB
oss-stable/react-test-renderer/umd/react-test-renderer.development.js +0.31% 854.14 kB 856.83 kB +0.36% 180.50 kB 181.15 kB
oss-experimental/react-art/umd/react-art.development.js +0.30% 960.30 kB 963.15 kB +0.35% 203.09 kB 203.80 kB
oss-experimental/react-reconciler/cjs/react-reconciler.development.js +0.29% 944.08 kB 946.84 kB +0.36% 202.45 kB 203.18 kB
oss-stable-semver/react-art/umd/react-art.development.js +0.29% 949.80 kB 952.56 kB +0.32% 201.28 kB 201.92 kB
oss-stable/react-art/umd/react-art.development.js +0.29% 949.83 kB 952.59 kB +0.32% 201.31 kB 201.95 kB
oss-stable-semver/react-reconciler/cjs/react-reconciler.development.js +0.29% 933.97 kB 936.63 kB +0.34% 200.55 kB 201.23 kB
oss-stable/react-reconciler/cjs/react-reconciler.development.js +0.29% 933.99 kB 936.66 kB +0.34% 200.58 kB 201.26 kB
oss-experimental/react-art/cjs/react-art.production.min.js +0.28% 99.91 kB 100.19 kB +0.37% 30.63 kB 30.74 kB
facebook-www/ReactDOM-prod.modern.js +0.28% 549.40 kB 550.92 kB +0.30% 96.60 kB 96.90 kB
facebook-www/ReactDOM-profiling.modern.js +0.27% 579.96 kB 581.51 kB +0.30% 101.19 kB 101.50 kB
facebook-www/ReactDOM-prod.classic.js +0.27% 565.55 kB 567.06 kB +0.28% 99.53 kB 99.81 kB
facebook-www/ReactDOMTesting-prod.modern.js +0.27% 565.81 kB 567.32 kB +0.28% 100.73 kB 101.01 kB
oss-experimental/react-test-renderer/cjs/react-test-renderer.production.min.js +0.26% 107.47 kB 107.76 kB +0.27% 32.96 kB 33.05 kB
oss-experimental/react-test-renderer/umd/react-test-renderer.production.min.js +0.26% 107.83 kB 108.12 kB +0.35% 33.33 kB 33.44 kB
facebook-www/ReactDOM-profiling.classic.js +0.26% 596.17 kB 597.72 kB +0.29% 104.05 kB 104.35 kB
facebook-www/ReactDOMTesting-prod.classic.js +0.26% 580.36 kB 581.87 kB +0.28% 103.26 kB 103.55 kB
oss-experimental/react-reconciler/cjs/react-reconciler.profiling.min.js +0.25% 125.80 kB 126.11 kB +0.32% 37.86 kB 37.98 kB
oss-experimental/react-reconciler/cjs/react-reconciler.production.min.js +0.24% 116.78 kB 117.06 kB +0.33% 35.60 kB 35.72 kB
oss-stable-semver/react-art/cjs/react-art.production.min.js +0.23% 98.01 kB 98.24 kB +0.34% 30.04 kB 30.14 kB
oss-stable/react-art/cjs/react-art.production.min.js +0.23% 98.06 kB 98.29 kB +0.34% 30.07 kB 30.17 kB
oss-stable-semver/react-test-renderer/cjs/react-test-renderer.production.min.js +0.21% 107.20 kB 107.42 kB +0.28% 32.85 kB 32.94 kB
oss-stable/react-test-renderer/cjs/react-test-renderer.production.min.js +0.21% 107.25 kB 107.48 kB +0.28% 32.88 kB 32.97 kB
oss-stable-semver/react-test-renderer/umd/react-test-renderer.production.min.js +0.21% 107.55 kB 107.78 kB +0.33% 33.23 kB 33.34 kB
oss-stable/react-test-renderer/umd/react-test-renderer.production.min.js +0.21% 107.60 kB 107.83 kB +0.35% 33.25 kB 33.37 kB
facebook-www/ReactDOM-dev.modern.js +0.21% 1,408.05 kB 1,410.98 kB +0.24% 304.93 kB 305.65 kB
oss-stable-semver/react-reconciler/cjs/react-reconciler.profiling.min.js +0.21% 123.89 kB 124.15 kB +0.24% 37.18 kB 37.27 kB
oss-stable/react-reconciler/cjs/react-reconciler.profiling.min.js +0.21% 123.92 kB 124.17 kB +0.24% 37.20 kB 37.29 kB
oss-experimental/react-dom/cjs/react-dom.development.js +0.21% 1,338.15 kB 1,340.91 kB +0.24% 295.04 kB 295.75 kB
oss-experimental/react-art/umd/react-art.production.min.js +0.21% 137.17 kB 137.45 kB +0.29% 42.77 kB 42.90 kB
facebook-www/ReactDOMTesting-dev.modern.js +0.21% 1,426.39 kB 1,429.33 kB +0.23% 309.37 kB 310.09 kB
facebook-www/ReactDOM-dev.classic.js +0.20% 1,435.95 kB 1,438.89 kB +0.23% 310.54 kB 311.27 kB
oss-experimental/react-dom/cjs/react-dom-unstable_testing.development.js +0.20% 1,356.21 kB 1,358.97 kB +0.23% 299.43 kB 300.12 kB
oss-experimental/react-dom/umd/react-dom.development.js +0.20% 1,402.58 kB 1,405.43 kB +0.23% 298.08 kB 298.78 kB
facebook-www/ReactDOMTesting-dev.classic.js +0.20% 1,454.30 kB 1,457.23 kB +0.23% 314.96 kB 315.68 kB
oss-stable-semver/react-dom/cjs/react-dom.development.js +0.20% 1,324.90 kB 1,327.56 kB +0.22% 292.63 kB 293.26 kB
oss-stable/react-dom/cjs/react-dom.development.js +0.20% 1,324.92 kB 1,327.59 kB +0.22% 292.65 kB 293.29 kB

Generated by 🚫 dangerJS against 66267d0

@acdlite acdlite force-pushed the udv-spawn-on-suspend branch 2 times, most recently from 8999797 to 89e8b89 Compare October 12, 2023 23:57
@acdlite acdlite marked this pull request as ready for review October 12, 2023 23:57
if (includesOnlyRetries(workInProgressRootRenderLanes)) {
// Retries are slightly lower priority than transitions, so if Retry task
// spawns a deferred task, the deferred task is also considered a Retry.
workInProgressDeferredLane = claimNextRetryLane();
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Still thinking about this... I'm not sure it's quite right because elsewhere we assume that a Retry task is only used to fill in Suspense fallbacks.

The only reason I have a special case here is because Retry is lower priority than Transitions, and I wanted to avoid a priority inversion. So it's mostly theoretical.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

yeah I'm going to revert this part and spawn as a Transition (like it does today) until I can think of a more compelling reason why it should be different.

I'll leave the Offscreen branch, though.

The Deferred lane will be used to mark deferred work that is spawned by
useDeferredValue during render. It doesn't have a priority on its own;
it will always be entangled with some other lane. That's also why we
don't need multiple deferred lanes.

We can check for the presence of the Deferred lane to quickly determine
if a render was spawned by useDeferredValue.
If a parent render spawns a deferred task with useDeferredValue, but
the parent render suspends, we should not wait for the parent render to
complete before attempting to render the final value.

The reason is that the initialValue argument to useDeferredValue is
meant to represent an immediate preview of the final UI. If we can't
render it "immediately", we might as well skip it and go straight to
the "real" value.

This is an improvement over how a userspace implementation of
useDeferredValue would work, because a userspace implementation would
have to wait for the parent task to commit (useEffect) before spawning
the deferred task, creating a waterfall.
@acdlite acdlite merged commit b2a68a6 into facebook:main Oct 17, 2023
36 checks passed
github-actions bot pushed a commit that referenced this pull request Oct 17, 2023
### Based on #27505

If a parent render spawns a deferred task with useDeferredValue, but the
parent render suspends, we should not wait for the parent render to
complete before attempting to render the final value.

The reason is that the initialValue argument to useDeferredValue is
meant to represent an immediate preview of the final UI. If we can't
render it "immediately", we might as well skip it and go straight to the
"real" value.

This is an improvement over how a userspace implementation of
useDeferredValue would work, because a userspace implementation would
have to wait for the parent task to commit (useEffect) before spawning
the deferred task, creating a waterfall.

DiffTrain build for [b2a68a6](b2a68a6)
acdlite added a commit that referenced this pull request Oct 17, 2023
### Based on #27509 

Revealing a prerendered tree (hidden -> visible) is considered the same
as mounting a brand new tree. So, when an initialValue argument is
passed to useDeferredValue, and it's prerendered inside a hidden tree,
we should first prerender the initial value.

After the initial value has been prerendered, we switch to prerendering
the final one. This is the same sequence that we use when mounting new
visible tree. Depending on how much prerendering work has been finished
by the time the tree is revealed, we may or may not be able to skip all
the way to the final value.

This means we get the benefits of both prerendering and preview states:
if we have enough resources to prerender the whole thing, we do that. If
we don't, we have a preview state to show for immediate feedback.
github-actions bot pushed a commit that referenced this pull request Oct 17, 2023
### Based on #27509

Revealing a prerendered tree (hidden -> visible) is considered the same
as mounting a brand new tree. So, when an initialValue argument is
passed to useDeferredValue, and it's prerendered inside a hidden tree,
we should first prerender the initial value.

After the initial value has been prerendered, we switch to prerendering
the final one. This is the same sequence that we use when mounting new
visible tree. Depending on how much prerendering work has been finished
by the time the tree is revealed, we may or may not be able to skip all
the way to the final value.

This means we get the benefits of both prerendering and preview states:
if we have enough resources to prerender the whole thing, we do that. If
we don't, we have a preview state to show for immediate feedback.

DiffTrain build for [75c1bd7](75c1bd7)
kodiakhq bot pushed a commit to vercel/next.js that referenced this pull request Oct 18, 2023
hoxyq added a commit that referenced this pull request Oct 18, 2023
Changes:
* fix[devtools/useMemoCache]: add stub for useMemoCache in
ReactDebugHook ([hoxyq](https://github.com/hoxyq) in
[#27472](#27472))
* useDeferredValue should skip initialValue if it suspends
([acdlite](https://github.com/acdlite) in
[#27509](#27509))
* feat[react-devtools-extensions/logging]: initialize session id on the
client for logging ([hoxyq](https://github.com/hoxyq) in
[#27517](#27517))
* refactor[react-devtools-extensions]: use globals to eliminate dead
code ([hoxyq](https://github.com/hoxyq) in
[#27516](#27516))
* fix[devtools/inspectElement]: dont pause initial inspectElement call
when user switches tabs ([hoxyq](https://github.com/hoxyq) in
[#27488](#27488))
EdisonVan pushed a commit to EdisonVan/react that referenced this pull request Apr 15, 2024
)

### Based on facebook#27505

If a parent render spawns a deferred task with useDeferredValue, but the
parent render suspends, we should not wait for the parent render to
complete before attempting to render the final value.

The reason is that the initialValue argument to useDeferredValue is
meant to represent an immediate preview of the final UI. If we can't
render it "immediately", we might as well skip it and go straight to the
"real" value.

This is an improvement over how a userspace implementation of
useDeferredValue would work, because a userspace implementation would
have to wait for the parent task to commit (useEffect) before spawning
the deferred task, creating a waterfall.
EdisonVan pushed a commit to EdisonVan/react that referenced this pull request Apr 15, 2024
### Based on facebook#27509 

Revealing a prerendered tree (hidden -> visible) is considered the same
as mounting a brand new tree. So, when an initialValue argument is
passed to useDeferredValue, and it's prerendered inside a hidden tree,
we should first prerender the initial value.

After the initial value has been prerendered, we switch to prerendering
the final one. This is the same sequence that we use when mounting new
visible tree. Depending on how much prerendering work has been finished
by the time the tree is revealed, we may or may not be able to skip all
the way to the final value.

This means we get the benefits of both prerendering and preview states:
if we have enough resources to prerender the whole thing, we do that. If
we don't, we have a preview state to show for immediate feedback.
EdisonVan pushed a commit to EdisonVan/react that referenced this pull request Apr 15, 2024
Changes:
* fix[devtools/useMemoCache]: add stub for useMemoCache in
ReactDebugHook ([hoxyq](https://github.com/hoxyq) in
[facebook#27472](facebook#27472))
* useDeferredValue should skip initialValue if it suspends
([acdlite](https://github.com/acdlite) in
[facebook#27509](facebook#27509))
* feat[react-devtools-extensions/logging]: initialize session id on the
client for logging ([hoxyq](https://github.com/hoxyq) in
[facebook#27517](facebook#27517))
* refactor[react-devtools-extensions]: use globals to eliminate dead
code ([hoxyq](https://github.com/hoxyq) in
[facebook#27516](facebook#27516))
* fix[devtools/inspectElement]: dont pause initial inspectElement call
when user switches tabs ([hoxyq](https://github.com/hoxyq) in
[facebook#27488](facebook#27488))
bigfootjon pushed a commit that referenced this pull request Apr 18, 2024
### Based on #27505

If a parent render spawns a deferred task with useDeferredValue, but the
parent render suspends, we should not wait for the parent render to
complete before attempting to render the final value.

The reason is that the initialValue argument to useDeferredValue is
meant to represent an immediate preview of the final UI. If we can't
render it "immediately", we might as well skip it and go straight to the
"real" value.

This is an improvement over how a userspace implementation of
useDeferredValue would work, because a userspace implementation would
have to wait for the parent task to commit (useEffect) before spawning
the deferred task, creating a waterfall.

DiffTrain build for commit b2a68a6.
bigfootjon pushed a commit that referenced this pull request Apr 18, 2024
### Based on #27509

Revealing a prerendered tree (hidden -> visible) is considered the same
as mounting a brand new tree. So, when an initialValue argument is
passed to useDeferredValue, and it's prerendered inside a hidden tree,
we should first prerender the initial value.

After the initial value has been prerendered, we switch to prerendering
the final one. This is the same sequence that we use when mounting new
visible tree. Depending on how much prerendering work has been finished
by the time the tree is revealed, we may or may not be able to skip all
the way to the final value.

This means we get the benefits of both prerendering and preview states:
if we have enough resources to prerender the whole thing, we do that. If
we don't, we have a preview state to show for immediate feedback.

DiffTrain build for commit 75c1bd7.
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