Skip to content

[Fiber] Trigger default indicator for isomorphic async actions with no root associated #33190

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

Merged
merged 3 commits into from
May 13, 2025

Conversation

sebmarkbage
Copy link
Collaborator

Stacked on #33160, #33162, #33186 and #33188.

We have a special case that's awkward for default indicators. When you start a new async Transition from React.startTransition then there's not yet any associated root with the Transition because you haven't necessarily setState on anything yet until the promise resolves. That's what entangleAsyncAction handles by creating a lane that everything entangles with until all async actions are done.

If there are no sync updates before the end of the event, we should trigger a default indicator until either the async action completes without update or if it gets entangled with some roots we should keep it going until those roots are done.

@github-actions github-actions bot added the React Core Team Opened by a member of the React Core Team label May 13, 2025
@react-sizebot
Copy link

react-sizebot commented May 13, 2025

Comparing: 5944042...ddb9d56

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 = 529.74 kB 529.74 kB = 93.49 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.28% 649.85 kB 651.70 kB +0.31% 114.34 kB 114.70 kB
facebook-www/ReactDOM-prod.classic.js = 675.93 kB 675.93 kB = 118.81 kB 118.81 kB
facebook-www/ReactDOM-prod.modern.js = 666.21 kB 666.21 kB = 117.18 kB 117.18 kB

Significant size changes

Includes any change greater than 0.2%:

Expand to show
Name +/- Base Current +/- gzip Base gzip Current gzip
oss-experimental/react-art/cjs/react-art.production.js +0.50% 350.61 kB 352.37 kB +0.52% 59.12 kB 59.43 kB
oss-experimental/react-reconciler/cjs/react-reconciler.production.js +0.42% 477.75 kB 479.73 kB +0.39% 76.28 kB 76.57 kB
oss-experimental/react-reconciler/cjs/react-reconciler.profiling.js +0.37% 537.80 kB 539.78 kB +0.39% 84.37 kB 84.69 kB
oss-experimental/react-dom/cjs/react-dom-client.production.js +0.28% 649.85 kB 651.70 kB +0.31% 114.34 kB 114.70 kB
oss-experimental/react-dom/cjs/react-dom-unstable_testing.production.js +0.28% 664.26 kB 666.11 kB +0.31% 117.92 kB 118.28 kB
oss-experimental/react-art/cjs/react-art.development.js +0.28% 672.37 kB 674.24 kB +0.34% 105.82 kB 106.18 kB
oss-experimental/react-reconciler/cjs/react-reconciler.development.js +0.26% 797.44 kB 799.55 kB +0.27% 124.25 kB 124.59 kB
oss-experimental/react-dom/cjs/react-dom-profiling.profiling.js +0.26% 711.99 kB 713.84 kB +0.31% 123.41 kB 123.79 kB

Generated by 🚫 dangerJS against ddb9d56

@sebmarkbage sebmarkbage force-pushed the transitionindicator5 branch from a23ca56 to b8a2521 Compare May 13, 2025 16:41
sebmarkbage added a commit that referenced this pull request May 13, 2025
Stacked on #33159.

This implements `onDefaultTransitionIndicator`.

The sequence is:

1) In `markRootUpdated` we schedule Transition updates as needing
`indicatorLanes` on the root. This tracks the lanes that currently need
an indicator to either start or remain going until this lane commits.
2) Track mutations during any commit. We use the same hook that view
transitions use here but instead of tracking it just per view transition
scope, we also track a global boolean for the whole root.
3) If a sync/default commit had any mutations, then we clear the
indicator lane for the `currentEventTransitionLane`. This requires that
the lane is still active while we do these commits. See #33159. In other
words, a sync update gets associated with the current transition and it
is assumed to be rendering the loading state for that corresponding
transition so we don't need a default indicator for this lane.
4) At the end of `processRootScheduleInMicrotask`, right before we're
about to enter a new "event transition lane" scope, it is no longer
possible to render any more loading states for the current transition
lane. That's when we invoke `onDefaultTransitionIndicator` for any roots
that have new indicator lanes.
5) When we commit, we remove the finished lanes from `indicatorLanes`
and once that reaches zero again, then we can clean up the default
indicator. This approach means that you can start multiple different
transitions while an indicator is still going but it won't stop/restart
each time. Instead, it'll wait until all are done before stopping.

Follow ups:

- [x] Default updates are currently not enough to cancel because those
aren't flush in the same microtask. That's unfortunate. #33186
- [x] Handle async actions before the setState. Since these don't
necessarily have a root this is tricky. #33190
- [x] Disable for `useDeferredValue`. ~Since it also goes through
`markRootUpdated` and schedules a Transition lane it'll get a default
indicator even though it probably shouldn't have one.~ EDIT: Turns out
this just works because it doesn't go through `markRootUpdated` when
work is left behind.
- [x] Implement built-in DOM version by default. #33162
github-actions bot pushed a commit that referenced this pull request May 13, 2025
Stacked on #33159.

This implements `onDefaultTransitionIndicator`.

The sequence is:

1) In `markRootUpdated` we schedule Transition updates as needing
`indicatorLanes` on the root. This tracks the lanes that currently need
an indicator to either start or remain going until this lane commits.
2) Track mutations during any commit. We use the same hook that view
transitions use here but instead of tracking it just per view transition
scope, we also track a global boolean for the whole root.
3) If a sync/default commit had any mutations, then we clear the
indicator lane for the `currentEventTransitionLane`. This requires that
the lane is still active while we do these commits. See #33159. In other
words, a sync update gets associated with the current transition and it
is assumed to be rendering the loading state for that corresponding
transition so we don't need a default indicator for this lane.
4) At the end of `processRootScheduleInMicrotask`, right before we're
about to enter a new "event transition lane" scope, it is no longer
possible to render any more loading states for the current transition
lane. That's when we invoke `onDefaultTransitionIndicator` for any roots
that have new indicator lanes.
5) When we commit, we remove the finished lanes from `indicatorLanes`
and once that reaches zero again, then we can clean up the default
indicator. This approach means that you can start multiple different
transitions while an indicator is still going but it won't stop/restart
each time. Instead, it'll wait until all are done before stopping.

Follow ups:

- [x] Default updates are currently not enough to cancel because those
aren't flush in the same microtask. That's unfortunate. #33186
- [x] Handle async actions before the setState. Since these don't
necessarily have a root this is tricky. #33190
- [x] Disable for `useDeferredValue`. ~Since it also goes through
`markRootUpdated` and schedules a Transition lane it'll get a default
indicator even though it probably shouldn't have one.~ EDIT: Turns out
this just works because it doesn't go through `markRootUpdated` when
work is left behind.
- [x] Implement built-in DOM version by default. #33162

DiffTrain build for [62d3f36](62d3f36)
github-actions bot pushed a commit that referenced this pull request May 13, 2025
Stacked on #33159.

This implements `onDefaultTransitionIndicator`.

The sequence is:

1) In `markRootUpdated` we schedule Transition updates as needing
`indicatorLanes` on the root. This tracks the lanes that currently need
an indicator to either start or remain going until this lane commits.
2) Track mutations during any commit. We use the same hook that view
transitions use here but instead of tracking it just per view transition
scope, we also track a global boolean for the whole root.
3) If a sync/default commit had any mutations, then we clear the
indicator lane for the `currentEventTransitionLane`. This requires that
the lane is still active while we do these commits. See #33159. In other
words, a sync update gets associated with the current transition and it
is assumed to be rendering the loading state for that corresponding
transition so we don't need a default indicator for this lane.
4) At the end of `processRootScheduleInMicrotask`, right before we're
about to enter a new "event transition lane" scope, it is no longer
possible to render any more loading states for the current transition
lane. That's when we invoke `onDefaultTransitionIndicator` for any roots
that have new indicator lanes.
5) When we commit, we remove the finished lanes from `indicatorLanes`
and once that reaches zero again, then we can clean up the default
indicator. This approach means that you can start multiple different
transitions while an indicator is still going but it won't stop/restart
each time. Instead, it'll wait until all are done before stopping.

Follow ups:

- [x] Default updates are currently not enough to cancel because those
aren't flush in the same microtask. That's unfortunate. #33186
- [x] Handle async actions before the setState. Since these don't
necessarily have a root this is tricky. #33190
- [x] Disable for `useDeferredValue`. ~Since it also goes through
`markRootUpdated` and schedules a Transition lane it'll get a default
indicator even though it probably shouldn't have one.~ EDIT: Turns out
this just works because it doesn't go through `markRootUpdated` when
work is left behind.
- [x] Implement built-in DOM version by default. #33162

DiffTrain build for [62d3f36](62d3f36)
Only do this if all the roots have the same onDefaultTransitionIndicator callback.
E.g. one root or multiple roots with the default.
@sebmarkbage sebmarkbage force-pushed the transitionindicator5 branch from b8a2521 to ddb9d56 Compare May 13, 2025 20:01
@sebmarkbage sebmarkbage merged commit 3a5b326 into facebook:main May 13, 2025
240 checks passed
github-actions bot pushed a commit that referenced this pull request May 13, 2025
…o root associated (#33190)

Stacked on #33160, #33162, #33186 and #33188.

We have a special case that's awkward for default indicators. When you
start a new async Transition from `React.startTransition` then there's
not yet any associated root with the Transition because you haven't
necessarily `setState` on anything yet until the promise resolves.
That's what `entangleAsyncAction` handles by creating a lane that
everything entangles with until all async actions are done.

If there are no sync updates before the end of the event, we should
trigger a default indicator until either the async action completes
without update or if it gets entangled with some roots we should keep it
going until those roots are done.

DiffTrain build for [3a5b326](3a5b326)
github-actions bot pushed a commit that referenced this pull request May 13, 2025
…o root associated (#33190)

Stacked on #33160, #33162, #33186 and #33188.

We have a special case that's awkward for default indicators. When you
start a new async Transition from `React.startTransition` then there's
not yet any associated root with the Transition because you haven't
necessarily `setState` on anything yet until the promise resolves.
That's what `entangleAsyncAction` handles by creating a lane that
everything entangles with until all async actions are done.

If there are no sync updates before the end of the event, we should
trigger a default indicator until either the async action completes
without update or if it gets entangled with some roots we should keep it
going until those roots are done.

DiffTrain build for [3a5b326](3a5b326)
github-actions bot pushed a commit to code/lib-react that referenced this pull request May 14, 2025
…o root associated (facebook#33190)

Stacked on facebook#33160, facebook#33162, facebook#33186 and facebook#33188.

We have a special case that's awkward for default indicators. When you
start a new async Transition from `React.startTransition` then there's
not yet any associated root with the Transition because you haven't
necessarily `setState` on anything yet until the promise resolves.
That's what `entangleAsyncAction` handles by creating a lane that
everything entangles with until all async actions are done.

If there are no sync updates before the end of the event, we should
trigger a default indicator until either the async action completes
without update or if it gets entangled with some roots we should keep it
going until those roots are done.

DiffTrain build for [3a5b326](facebook@3a5b326)
github-actions bot pushed a commit to code/lib-react that referenced this pull request May 14, 2025
…o root associated (facebook#33190)

Stacked on facebook#33160, facebook#33162, facebook#33186 and facebook#33188.

We have a special case that's awkward for default indicators. When you
start a new async Transition from `React.startTransition` then there's
not yet any associated root with the Transition because you haven't
necessarily `setState` on anything yet until the promise resolves.
That's what `entangleAsyncAction` handles by creating a lane that
everything entangles with until all async actions are done.

If there are no sync updates before the end of the event, we should
trigger a default indicator until either the async action completes
without update or if it gets entangled with some roots we should keep it
going until those roots are done.

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