@@ -37,7 +37,6 @@ import type {UpdateQueue} from './ReactFiberClassUpdateQueue.new';
37
37
import type { RootState } from './ReactFiberRoot.new' ;
38
38
import type { TracingMarkerInstance } from './ReactFiberTracingMarkerComponent.new' ;
39
39
import {
40
- enableSuspenseAvoidThisFallback ,
41
40
enableCPUSuspense ,
42
41
enableUseMutableSource ,
43
42
} from 'shared/ReactFeatureFlags' ;
@@ -167,13 +166,14 @@ import {shouldError, shouldSuspend} from './ReactFiberReconciler';
167
166
import { pushHostContext , pushHostContainer } from './ReactFiberHostContext.new' ;
168
167
import {
169
168
suspenseStackCursor ,
170
- pushSuspenseContext ,
171
- InvisibleParentSuspenseContext ,
169
+ pushSuspenseListContext ,
172
170
ForceSuspenseFallback ,
173
- hasSuspenseContext ,
174
- setDefaultShallowSuspenseContext ,
175
- addSubtreeSuspenseContext ,
176
- setShallowSuspenseContext ,
171
+ hasSuspenseListContext ,
172
+ setDefaultShallowSuspenseListContext ,
173
+ setShallowSuspenseListContext ,
174
+ pushPrimaryTreeSuspenseHandler ,
175
+ pushFallbackTreeSuspenseHandler ,
176
+ popSuspenseHandler ,
177
177
} from './ReactFiberSuspenseContext.new' ;
178
178
import {
179
179
pushHiddenContext ,
@@ -1969,7 +1969,6 @@ function updateSuspenseOffscreenState(
1969
1969
1970
1970
// TODO: Probably should inline this back
1971
1971
function shouldRemainOnFallback (
1972
- suspenseContext : SuspenseContext ,
1973
1972
current : null | Fiber ,
1974
1973
workInProgress : Fiber ,
1975
1974
renderLanes : Lanes ,
@@ -1989,7 +1988,8 @@ function shouldRemainOnFallback(
1989
1988
}
1990
1989
1991
1990
// Not currently showing content. Consult the Suspense context.
1992
- return hasSuspenseContext (
1991
+ const suspenseContext : SuspenseContext = suspenseStackCursor . current ;
1992
+ return hasSuspenseListContext (
1993
1993
suspenseContext ,
1994
1994
( ForceSuspenseFallback : SuspenseContext ) ,
1995
1995
) ;
@@ -2010,50 +2010,18 @@ function updateSuspenseComponent(current, workInProgress, renderLanes) {
2010
2010
}
2011
2011
}
2012
2012
2013
- let suspenseContext : SuspenseContext = suspenseStackCursor . current ;
2014
-
2015
2013
let showFallback = false ;
2016
2014
const didSuspend = ( workInProgress . flags & DidCapture ) !== NoFlags ;
2017
-
2018
2015
if (
2019
2016
didSuspend ||
2020
- shouldRemainOnFallback (
2021
- suspenseContext ,
2022
- current ,
2023
- workInProgress ,
2024
- renderLanes ,
2025
- )
2017
+ shouldRemainOnFallback ( current , workInProgress , renderLanes )
2026
2018
) {
2027
2019
// Something in this boundary's subtree already suspended. Switch to
2028
2020
// rendering the fallback children.
2029
2021
showFallback = true ;
2030
2022
workInProgress . flags &= ~ DidCapture ;
2031
- } else {
2032
- // Attempting the main content
2033
- if (
2034
- current === null ||
2035
- ( current . memoizedState : null | SuspenseState ) !== null
2036
- ) {
2037
- // This is a new mount or this boundary is already showing a fallback state.
2038
- // Mark this subtree context as having at least one invisible parent that could
2039
- // handle the fallback state.
2040
- // Avoided boundaries are not considered since they cannot handle preferred fallback states.
2041
- if (
2042
- ! enableSuspenseAvoidThisFallback ||
2043
- nextProps . unstable_avoidThisFallback !== true
2044
- ) {
2045
- suspenseContext = addSubtreeSuspenseContext (
2046
- suspenseContext ,
2047
- InvisibleParentSuspenseContext ,
2048
- ) ;
2049
- }
2050
- }
2051
2023
}
2052
2024
2053
- suspenseContext = setDefaultShallowSuspenseContext ( suspenseContext ) ;
2054
-
2055
- pushSuspenseContext ( workInProgress , suspenseContext ) ;
2056
-
2057
2025
// OK, the next part is confusing. We're about to reconcile the Suspense
2058
2026
// boundary's children. This involves some custom reconciliation logic. Two
2059
2027
// main reasons this is so complicated.
@@ -2081,24 +2049,40 @@ function updateSuspenseComponent(current, workInProgress, renderLanes) {
2081
2049
2082
2050
// Special path for hydration
2083
2051
// If we're currently hydrating, try to hydrate this boundary.
2084
- tryToClaimNextHydratableInstance ( workInProgress ) ;
2085
- // This could've been a dehydrated suspense component.
2086
- const suspenseState : null | SuspenseState = workInProgress . memoizedState ;
2087
- if ( suspenseState !== null ) {
2088
- const dehydrated = suspenseState . dehydrated ;
2089
- if ( dehydrated !== null ) {
2090
- return mountDehydratedSuspenseComponent (
2091
- workInProgress ,
2092
- dehydrated ,
2093
- renderLanes ,
2094
- ) ;
2052
+ if ( getIsHydrating ( ) ) {
2053
+ // We must push the suspense handler context *before* attempting to
2054
+ // hydrate, to avoid a mismatch in case it errors.
2055
+ if ( showFallback ) {
2056
+ pushPrimaryTreeSuspenseHandler ( workInProgress ) ;
2057
+ } else {
2058
+ pushFallbackTreeSuspenseHandler ( workInProgress ) ;
2059
+ }
2060
+ tryToClaimNextHydratableInstance ( workInProgress ) ;
2061
+ // This could've been a dehydrated suspense component.
2062
+ const suspenseState : null | SuspenseState = workInProgress . memoizedState ;
2063
+ if ( suspenseState !== null ) {
2064
+ const dehydrated = suspenseState . dehydrated ;
2065
+ if ( dehydrated !== null ) {
2066
+ return mountDehydratedSuspenseComponent (
2067
+ workInProgress ,
2068
+ dehydrated ,
2069
+ renderLanes ,
2070
+ ) ;
2071
+ }
2095
2072
}
2073
+ // If hydration didn't succeed, fall through to the normal Suspense path.
2074
+ // To avoid a stack mismatch we need to pop the Suspense handler that we
2075
+ // pushed above. This will become less awkward when move the hydration
2076
+ // logic to its own fiber.
2077
+ popSuspenseHandler ( workInProgress ) ;
2096
2078
}
2097
2079
2098
2080
const nextPrimaryChildren = nextProps . children ;
2099
2081
const nextFallbackChildren = nextProps . fallback ;
2100
2082
2101
2083
if ( showFallback ) {
2084
+ pushFallbackTreeSuspenseHandler ( workInProgress ) ;
2085
+
2102
2086
const fallbackFragment = mountSuspenseFallbackChildren (
2103
2087
workInProgress ,
2104
2088
nextPrimaryChildren ,
@@ -2131,6 +2115,7 @@ function updateSuspenseComponent(current, workInProgress, renderLanes) {
2131
2115
// This is a CPU-bound tree. Skip this tree and show a placeholder to
2132
2116
// unblock the surrounding content. Then immediately retry after the
2133
2117
// initial commit.
2118
+ pushFallbackTreeSuspenseHandler ( workInProgress ) ;
2134
2119
const fallbackFragment = mountSuspenseFallbackChildren (
2135
2120
workInProgress ,
2136
2121
nextPrimaryChildren ,
@@ -2154,6 +2139,7 @@ function updateSuspenseComponent(current, workInProgress, renderLanes) {
2154
2139
workInProgress . lanes = SomeRetryLane ;
2155
2140
return fallbackFragment ;
2156
2141
} else {
2142
+ pushPrimaryTreeSuspenseHandler ( workInProgress ) ;
2157
2143
return mountSuspensePrimaryChildren (
2158
2144
workInProgress ,
2159
2145
nextPrimaryChildren ,
@@ -2181,6 +2167,8 @@ function updateSuspenseComponent(current, workInProgress, renderLanes) {
2181
2167
}
2182
2168
2183
2169
if ( showFallback ) {
2170
+ pushFallbackTreeSuspenseHandler ( workInProgress ) ;
2171
+
2184
2172
const nextFallbackChildren = nextProps . fallback ;
2185
2173
const nextPrimaryChildren = nextProps . children ;
2186
2174
const fallbackChildFragment = updateSuspenseFallbackChildren (
@@ -2215,6 +2203,8 @@ function updateSuspenseComponent(current, workInProgress, renderLanes) {
2215
2203
workInProgress . memoizedState = SUSPENDED_MARKER ;
2216
2204
return fallbackChildFragment ;
2217
2205
} else {
2206
+ pushPrimaryTreeSuspenseHandler ( workInProgress ) ;
2207
+
2218
2208
const nextPrimaryChildren = nextProps . children ;
2219
2209
const primaryChildFragment = updateSuspensePrimaryChildren (
2220
2210
current ,
@@ -2585,6 +2575,7 @@ function updateDehydratedSuspenseComponent(
2585
2575
) : null | Fiber {
2586
2576
if ( ! didSuspend ) {
2587
2577
// This is the first render pass. Attempt to hydrate.
2578
+ pushPrimaryTreeSuspenseHandler ( workInProgress ) ;
2588
2579
2589
2580
// We should never be hydrating at this point because it is the first pass,
2590
2581
// but after we've already committed once.
@@ -2751,6 +2742,8 @@ function updateDehydratedSuspenseComponent(
2751
2742
2752
2743
if ( workInProgress . flags & ForceClientRender ) {
2753
2744
// Something errored during hydration. Try again without hydrating.
2745
+ pushPrimaryTreeSuspenseHandler ( workInProgress ) ;
2746
+
2754
2747
workInProgress . flags &= ~ ForceClientRender ;
2755
2748
const capturedValue = createCapturedValue (
2756
2749
new Error (
@@ -2767,6 +2760,10 @@ function updateDehydratedSuspenseComponent(
2767
2760
} else if ( ( workInProgress . memoizedState : null | SuspenseState ) !== null ) {
2768
2761
// Something suspended and we should still be in dehydrated mode.
2769
2762
// Leave the existing child in place.
2763
+
2764
+ // Push to avoid a mismatch
2765
+ pushFallbackTreeSuspenseHandler ( workInProgress ) ;
2766
+
2770
2767
workInProgress . child = current . child ;
2771
2768
// The dehydrated completion pass expects this flag to be there
2772
2769
// but the normal suspense pass doesn't.
@@ -2775,6 +2772,8 @@ function updateDehydratedSuspenseComponent(
2775
2772
} else {
2776
2773
// Suspended but we should no longer be in dehydrated mode.
2777
2774
// Therefore we now have to render the fallback.
2775
+ pushFallbackTreeSuspenseHandler ( workInProgress ) ;
2776
+
2778
2777
const nextPrimaryChildren = nextProps . children ;
2779
2778
const nextFallbackChildren = nextProps . fallback ;
2780
2779
const fallbackChildFragment = mountSuspenseFallbackAfterRetryWithoutHydrating (
@@ -3070,12 +3069,12 @@ function updateSuspenseListComponent(
3070
3069
3071
3070
let suspenseContext : SuspenseContext = suspenseStackCursor . current ;
3072
3071
3073
- const shouldForceFallback = hasSuspenseContext (
3072
+ const shouldForceFallback = hasSuspenseListContext (
3074
3073
suspenseContext ,
3075
3074
( ForceSuspenseFallback : SuspenseContext ) ,
3076
3075
) ;
3077
3076
if ( shouldForceFallback ) {
3078
- suspenseContext = setShallowSuspenseContext (
3077
+ suspenseContext = setShallowSuspenseListContext (
3079
3078
suspenseContext ,
3080
3079
ForceSuspenseFallback ,
3081
3080
) ;
@@ -3093,9 +3092,9 @@ function updateSuspenseListComponent(
3093
3092
renderLanes ,
3094
3093
) ;
3095
3094
}
3096
- suspenseContext = setDefaultShallowSuspenseContext ( suspenseContext ) ;
3095
+ suspenseContext = setDefaultShallowSuspenseListContext ( suspenseContext ) ;
3097
3096
}
3098
- pushSuspenseContext ( workInProgress , suspenseContext ) ;
3097
+ pushSuspenseListContext ( workInProgress , suspenseContext ) ;
3099
3098
3100
3099
if ( ( workInProgress . mode & ConcurrentMode ) === NoMode ) {
3101
3100
// In legacy mode, SuspenseList doesn't work so we just
@@ -3559,10 +3558,9 @@ function attemptEarlyBailoutIfNoScheduledUpdate(
3559
3558
const state : SuspenseState | null = workInProgress . memoizedState ;
3560
3559
if ( state !== null ) {
3561
3560
if ( state . dehydrated !== null ) {
3562
- pushSuspenseContext (
3563
- workInProgress ,
3564
- setDefaultShallowSuspenseContext ( suspenseStackCursor . current ) ,
3565
- ) ;
3561
+ // We're not going to render the children, so this is just to maintain
3562
+ // push/pop symmetry
3563
+ pushPrimaryTreeSuspenseHandler ( workInProgress ) ;
3566
3564
// We know that this component will suspend again because if it has
3567
3565
// been unsuspended it has committed as a resolved Suspense component.
3568
3566
// If it needs to be retried, it should have work scheduled on it.
@@ -3585,10 +3583,7 @@ function attemptEarlyBailoutIfNoScheduledUpdate(
3585
3583
} else {
3586
3584
// The primary child fragment does not have pending work marked
3587
3585
// on it
3588
- pushSuspenseContext (
3589
- workInProgress ,
3590
- setDefaultShallowSuspenseContext ( suspenseStackCursor . current ) ,
3591
- ) ;
3586
+ pushPrimaryTreeSuspenseHandler ( workInProgress ) ;
3592
3587
// The primary children do not have pending work with sufficient
3593
3588
// priority. Bailout.
3594
3589
const child = bailoutOnAlreadyFinishedWork (
@@ -3608,10 +3603,7 @@ function attemptEarlyBailoutIfNoScheduledUpdate(
3608
3603
}
3609
3604
}
3610
3605
} else {
3611
- pushSuspenseContext (
3612
- workInProgress ,
3613
- setDefaultShallowSuspenseContext ( suspenseStackCursor . current ) ,
3614
- ) ;
3606
+ pushPrimaryTreeSuspenseHandler ( workInProgress ) ;
3615
3607
}
3616
3608
break ;
3617
3609
}
@@ -3669,7 +3661,7 @@ function attemptEarlyBailoutIfNoScheduledUpdate(
3669
3661
renderState . tail = null ;
3670
3662
renderState . lastEffect = null ;
3671
3663
}
3672
- pushSuspenseContext ( workInProgress , suspenseStackCursor . current ) ;
3664
+ pushSuspenseListContext ( workInProgress , suspenseStackCursor . current ) ;
3673
3665
3674
3666
if ( hasChildWork ) {
3675
3667
break ;
0 commit comments