[Fiber] SuspenseList with "hidden" tail row should "catch" suspense#35042
Merged
Conversation
We have to be careful not to drop existing Fibers from the list.
|
Comparing: d000261...7d82cbc Critical size changesIncludes critical production bundles, as well as any change greater than 2%:
Significant size changesIncludes any change greater than 0.2%: Expand to show |
sebmarkbage
commented
Nov 4, 2025
| } | ||
| case RootSuspendedWithDelay: { | ||
| if (!includesOnlyTransitions(lanes)) { | ||
| if (!includesOnlyTransitions(lanes) && !includesOnlyRetries(lanes)) { |
Contributor
Author
There was a problem hiding this comment.
This is the case that fixes "collapsed" mode. Because otherwise when we render a case that would lead to an undesirable state (a previous row unsuspends which now suspends the next row) to actually commit today.
acdlite
approved these changes
Nov 5, 2025
github-actions Bot
pushed a commit
that referenced
this pull request
Nov 5, 2025
…35042) Normally if you suspend in a SuspenseList row above a Suspense boundary in that row, it'll suspend the parent. Which can itself delay the commit or resuspend a parent boundary. That's because SuspenseList mostly just coordinates the state of the inner boundaries and isn't a boundary itself. However, for tail "hidden" and "collapsed" this is not quite the case because the rows themselves can avoid being rendered. In the case of "collapsed" we require at least one Suspense boundary above to have successfully rendered before committing the list because the idea of this mode is that you should at least always show some indicator that things are still loading. Since we'd never try the next one after that at all, this just works. Expect there was an unrelated bug that meant that "suspend with delay" on a Retry didn't suspend the commit. This caused a scenario were it'd allow a commit proceed when it shouldn't. So I fixed that too. The counter intuitive thing here is that we won't actually show a previous completed row if the loading state of the next row is still loading. For tail "hidden" it's a little different because we don't actually require any loading indicator at all to be shown while it's loading. If we attempt a row and it suspends, we can just hide it (and the rest) and move to commit. Therefore this implements a path where if all the rest of the tail are new mounts (we wouldn't be required to unmount any existing boundaries) then we can treat the SuspenseList boundary itself as "catching" the suspense. This is more coherent semantics since any future row that we didn't attempt also wouldn't resuspend the parent. This allows simple cases like `<SuspenseList>{list}</SuspenseList>` to stream in each row without any indicator and no need for Suspense boundaries. DiffTrain build for [986323f](986323f)
github-actions Bot
pushed a commit
that referenced
this pull request
Nov 5, 2025
…35042) Normally if you suspend in a SuspenseList row above a Suspense boundary in that row, it'll suspend the parent. Which can itself delay the commit or resuspend a parent boundary. That's because SuspenseList mostly just coordinates the state of the inner boundaries and isn't a boundary itself. However, for tail "hidden" and "collapsed" this is not quite the case because the rows themselves can avoid being rendered. In the case of "collapsed" we require at least one Suspense boundary above to have successfully rendered before committing the list because the idea of this mode is that you should at least always show some indicator that things are still loading. Since we'd never try the next one after that at all, this just works. Expect there was an unrelated bug that meant that "suspend with delay" on a Retry didn't suspend the commit. This caused a scenario were it'd allow a commit proceed when it shouldn't. So I fixed that too. The counter intuitive thing here is that we won't actually show a previous completed row if the loading state of the next row is still loading. For tail "hidden" it's a little different because we don't actually require any loading indicator at all to be shown while it's loading. If we attempt a row and it suspends, we can just hide it (and the rest) and move to commit. Therefore this implements a path where if all the rest of the tail are new mounts (we wouldn't be required to unmount any existing boundaries) then we can treat the SuspenseList boundary itself as "catching" the suspense. This is more coherent semantics since any future row that we didn't attempt also wouldn't resuspend the parent. This allows simple cases like `<SuspenseList>{list}</SuspenseList>` to stream in each row without any indicator and no need for Suspense boundaries. DiffTrain build for [986323f](986323f)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Normally if you suspend in a SuspenseList row above a Suspense boundary in that row, it'll suspend the parent. Which can itself delay the commit or resuspend a parent boundary. That's because SuspenseList mostly just coordinates the state of the inner boundaries and isn't a boundary itself.
However, for tail "hidden" and "collapsed" this is not quite the case because the rows themselves can avoid being rendered.
In the case of "collapsed" we require at least one Suspense boundary above to have successfully rendered before committing the list because the idea of this mode is that you should at least always show some indicator that things are still loading. Since we'd never try the next one after that at all, this just works. Expect there was an unrelated bug that meant that "suspend with delay" on a Retry didn't suspend the commit. This caused a scenario were it'd allow a commit proceed when it shouldn't. So I fixed that too. The counter intuitive thing here is that we won't actually show a previous completed row if the loading state of the next row is still loading.
For tail "hidden" it's a little different because we don't actually require any loading indicator at all to be shown while it's loading. If we attempt a row and it suspends, we can just hide it (and the rest) and move to commit. Therefore this implements a path where if all the rest of the tail are new mounts (we wouldn't be required to unmount any existing boundaries) then we can treat the SuspenseList boundary itself as "catching" the suspense. This is more coherent semantics since any future row that we didn't attempt also wouldn't resuspend the parent.
This allows simple cases like
<SuspenseList>{list}</SuspenseList>to stream in each row without any indicator and no need for Suspense boundaries.