Skip to content

Conversation

acdlite
Copy link
Collaborator

@acdlite acdlite commented Sep 3, 2025

Fixes a bug in useDeferredValue's optional initialValue argument. In the regression case, if a new useDeferredValue hook is mounted while an earlier transition is suspended, the initialValue argument of the new hook was ignored. After the fix, the initialValue argument is correctly rendered during the initial mount, regardless of whether other transitions were suspended.

The culprit was related to the mechanism we use to track whether a render is the result of a useDeferredValue hook: we assign the deferred lane a TransitionLane, then entangle that lane with the DeferredLane bit. During the subsequent render, we check for the presence of the DeferredLane bit to determine whether to switch to the final, canonical value.

But because transition lanes can themselves become entangled with other transitions, the effect is that every entangled transition was being treated as if it were the result of a useDeferredValue hook, causing us to skip the initial value and go straight to the final one.

The fix I've chosen is to reserve some subset of TransitionLanes to be used only for deferred work, instead of using entanglement. This is similar to how retries are already implemented. Originally I tried not to implement it this way because it means there are now slightly fewer lanes allocated for regular transitions, but I underestimated how similar deferred work is to retries; they end up having a lot of the same requirements. Eventually it may be possible to merge the two concepts.

@meta-cla meta-cla bot added the CLA Signed label Sep 3, 2025
@github-actions github-actions bot added the React Core Team Opened by a member of the React Core Team label Sep 3, 2025
@acdlite acdlite requested a review from sebmarkbage September 3, 2025 21:56
@react-sizebot
Copy link

react-sizebot commented Sep 3, 2025

Comparing: 7697a9f...a45a354

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.js = 6.68 kB 6.68 kB = 1.83 kB 1.83 kB
oss-stable/react-dom/cjs/react-dom-client.production.js +0.06% 530.31 kB 530.65 kB +0.11% 93.39 kB 93.49 kB
oss-experimental/react-dom/cjs/react-dom.production.js = 6.69 kB 6.69 kB = 1.83 kB 1.83 kB
oss-experimental/react-dom/cjs/react-dom-client.production.js +0.05% 657.78 kB 658.12 kB +0.10% 115.66 kB 115.77 kB
facebook-www/ReactDOM-prod.classic.js +0.05% 681.91 kB 682.25 kB +0.09% 119.69 kB 119.80 kB
facebook-www/ReactDOM-prod.modern.js +0.05% 672.34 kB 672.68 kB +0.09% 118.00 kB 118.11 kB

Significant size changes

Includes any change greater than 0.2%:

(No significant changes)

Generated by 🚫 dangerJS against a45a354

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.

Sick find

Fixes a bug in useDeferredValue's optional `initialValue` argument. In
the regression case, if a new useDeferredValue hook is mounted while an
earlier transition is suspended, the `initialValue` argument of the
new hook was ignored. After the fix, the `initialValue` argument is
correctly rendered during the initial mount, regardless of whether
other transitions were suspended.

The culprit was related to the mechanism we use to track whether a
render is the result of a `useDeferredValue` hook: we assign the
deferred lane a TransitionLane, then entangle that lane with the
DeferredLane bit. During the subsequent render, we check for the
presence of the DeferredLane bit to determine whether to switch to
the final, canonical value.

But because transition lanes can themselves become entangled with
other transitions, the effect is that every entangled transition was
being treated as if it were the result of a `useDeferredValue` hook,
causing us to skip the initial value and go straight to the final one.

The fix I've chosen is to reserve some subset of TransitionLanes to be
used only for deferred work, instead of using entanglement. This is
similar to how retries are already implemented. Originally I tried not
to implement it this way because it means there are now slightly fewer
lanes allocated for regular transitions, but I underestimated how
similar deferred work is to retries; they end  up having a lot of the
same requirements. Eventually it may be possible to merge the
two concepts.
@acdlite acdlite force-pushed the fix-udv-skipped-initial-value branch from f61b963 to a45a354 Compare September 3, 2025 23:12
@acdlite acdlite merged commit 3302d1f into facebook:main Sep 3, 2025
241 checks passed
github-actions bot pushed a commit that referenced this pull request Sep 3, 2025
Fixes a bug in useDeferredValue's optional `initialValue` argument. In
the regression case, if a new useDeferredValue hook is mounted while an
earlier transition is suspended, the `initialValue` argument of the new
hook was ignored. After the fix, the `initialValue` argument is
correctly rendered during the initial mount, regardless of whether other
transitions were suspended.

The culprit was related to the mechanism we use to track whether a
render is the result of a `useDeferredValue` hook: we assign the
deferred lane a TransitionLane, then entangle that lane with the
DeferredLane bit. During the subsequent render, we check for the
presence of the DeferredLane bit to determine whether to switch to the
final, canonical value.

But because transition lanes can themselves become entangled with other
transitions, the effect is that every entangled transition was being
treated as if it were the result of a `useDeferredValue` hook, causing
us to skip the initial value and go straight to the final one.

The fix I've chosen is to reserve some subset of TransitionLanes to be
used only for deferred work, instead of using entanglement. This is
similar to how retries are already implemented. Originally I tried not
to implement it this way because it means there are now slightly fewer
lanes allocated for regular transitions, but I underestimated how
similar deferred work is to retries; they end up having a lot of the
same requirements. Eventually it may be possible to merge the two
concepts.

DiffTrain build for [3302d1f](3302d1f)
github-actions bot pushed a commit that referenced this pull request Sep 3, 2025
Fixes a bug in useDeferredValue's optional `initialValue` argument. In
the regression case, if a new useDeferredValue hook is mounted while an
earlier transition is suspended, the `initialValue` argument of the new
hook was ignored. After the fix, the `initialValue` argument is
correctly rendered during the initial mount, regardless of whether other
transitions were suspended.

The culprit was related to the mechanism we use to track whether a
render is the result of a `useDeferredValue` hook: we assign the
deferred lane a TransitionLane, then entangle that lane with the
DeferredLane bit. During the subsequent render, we check for the
presence of the DeferredLane bit to determine whether to switch to the
final, canonical value.

But because transition lanes can themselves become entangled with other
transitions, the effect is that every entangled transition was being
treated as if it were the result of a `useDeferredValue` hook, causing
us to skip the initial value and go straight to the final one.

The fix I've chosen is to reserve some subset of TransitionLanes to be
used only for deferred work, instead of using entanglement. This is
similar to how retries are already implemented. Originally I tried not
to implement it this way because it means there are now slightly fewer
lanes allocated for regular transitions, but I underestimated how
similar deferred work is to retries; they end up having a lot of the
same requirements. Eventually it may be possible to merge the two
concepts.

DiffTrain build for [3302d1f](3302d1f)
acdlite pushed a commit to vercel/next.js that referenced this pull request Sep 4, 2025
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.

It's probably worth just making this use the Retry lane like we initially thought since they're conceptually very similar. The idea is to commit the first Transition basically instantly and then have a follow up to fill in the rest.

It's kind of interesting how this should be visualized in the Performance Track but I think it would actually make sense to treat this case a the Suspense track (Retry) instead of the Transition track since it's really about tracking an update which might itself act as a thing that resolves a Suspense boundary. So arguably it's confusing to visualize this as a Transition like it would be now.

EugeneChoi4 pushed a commit to EugeneChoi4/react that referenced this pull request Sep 4, 2025
…ook#34376)

Fixes a bug in useDeferredValue's optional `initialValue` argument. In
the regression case, if a new useDeferredValue hook is mounted while an
earlier transition is suspended, the `initialValue` argument of the new
hook was ignored. After the fix, the `initialValue` argument is
correctly rendered during the initial mount, regardless of whether other
transitions were suspended.

The culprit was related to the mechanism we use to track whether a
render is the result of a `useDeferredValue` hook: we assign the
deferred lane a TransitionLane, then entangle that lane with the
DeferredLane bit. During the subsequent render, we check for the
presence of the DeferredLane bit to determine whether to switch to the
final, canonical value.

But because transition lanes can themselves become entangled with other
transitions, the effect is that every entangled transition was being
treated as if it were the result of a `useDeferredValue` hook, causing
us to skip the initial value and go straight to the final one.

The fix I've chosen is to reserve some subset of TransitionLanes to be
used only for deferred work, instead of using entanglement. This is
similar to how retries are already implemented. Originally I tried not
to implement it this way because it means there are now slightly fewer
lanes allocated for regular transitions, but I underestimated how
similar deferred work is to retries; they end up having a lot of the
same requirements. Eventually it may be possible to merge the two
concepts.
github-actions bot pushed a commit to code/lib-react that referenced this pull request Sep 7, 2025
…ook#34376)

Fixes a bug in useDeferredValue's optional `initialValue` argument. In
the regression case, if a new useDeferredValue hook is mounted while an
earlier transition is suspended, the `initialValue` argument of the new
hook was ignored. After the fix, the `initialValue` argument is
correctly rendered during the initial mount, regardless of whether other
transitions were suspended.

The culprit was related to the mechanism we use to track whether a
render is the result of a `useDeferredValue` hook: we assign the
deferred lane a TransitionLane, then entangle that lane with the
DeferredLane bit. During the subsequent render, we check for the
presence of the DeferredLane bit to determine whether to switch to the
final, canonical value.

But because transition lanes can themselves become entangled with other
transitions, the effect is that every entangled transition was being
treated as if it were the result of a `useDeferredValue` hook, causing
us to skip the initial value and go straight to the final one.

The fix I've chosen is to reserve some subset of TransitionLanes to be
used only for deferred work, instead of using entanglement. This is
similar to how retries are already implemented. Originally I tried not
to implement it this way because it means there are now slightly fewer
lanes allocated for regular transitions, but I underestimated how
similar deferred work is to retries; they end up having a lot of the
same requirements. Eventually it may be possible to merge the two
concepts.

DiffTrain build for [3302d1f](facebook@3302d1f)
github-actions bot pushed a commit to code/lib-react that referenced this pull request Sep 7, 2025
…ook#34376)

Fixes a bug in useDeferredValue's optional `initialValue` argument. In
the regression case, if a new useDeferredValue hook is mounted while an
earlier transition is suspended, the `initialValue` argument of the new
hook was ignored. After the fix, the `initialValue` argument is
correctly rendered during the initial mount, regardless of whether other
transitions were suspended.

The culprit was related to the mechanism we use to track whether a
render is the result of a `useDeferredValue` hook: we assign the
deferred lane a TransitionLane, then entangle that lane with the
DeferredLane bit. During the subsequent render, we check for the
presence of the DeferredLane bit to determine whether to switch to the
final, canonical value.

But because transition lanes can themselves become entangled with other
transitions, the effect is that every entangled transition was being
treated as if it were the result of a `useDeferredValue` hook, causing
us to skip the initial value and go straight to the final one.

The fix I've chosen is to reserve some subset of TransitionLanes to be
used only for deferred work, instead of using entanglement. This is
similar to how retries are already implemented. Originally I tried not
to implement it this way because it means there are now slightly fewer
lanes allocated for regular transitions, but I underestimated how
similar deferred work is to retries; they end up having a lot of the
same requirements. Eventually it may be possible to merge the two
concepts.

DiffTrain build for [3302d1f](facebook@3302d1f)
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