@@ -36,7 +36,6 @@ import type {
36
36
import type { UpdateQueue } from './ReactUpdateQueue.new' ;
37
37
import type { RootState } from './ReactFiberRoot.new' ;
38
38
import {
39
- enableSuspenseAvoidThisFallback ,
40
39
enableCPUSuspense ,
41
40
enableUseMutableSource ,
42
41
} from 'shared/ReactFeatureFlags' ;
@@ -166,13 +165,14 @@ import {shouldError, shouldSuspend} from './ReactFiberReconciler';
166
165
import { pushHostContext , pushHostContainer } from './ReactFiberHostContext.new' ;
167
166
import {
168
167
suspenseStackCursor ,
169
- pushSuspenseContext ,
170
- InvisibleParentSuspenseContext ,
168
+ pushSuspenseListContext ,
171
169
ForceSuspenseFallback ,
172
- hasSuspenseContext ,
173
- setDefaultShallowSuspenseContext ,
174
- addSubtreeSuspenseContext ,
175
- setShallowSuspenseContext ,
170
+ hasSuspenseListContext ,
171
+ setDefaultShallowSuspenseListContext ,
172
+ setShallowSuspenseListContext ,
173
+ pushPrimaryTreeSuspenseHandler ,
174
+ pushFallbackTreeSuspenseHandler ,
175
+ popSuspenseHandler ,
176
176
} from './ReactFiberSuspenseContext.new' ;
177
177
import {
178
178
pushHiddenContext ,
@@ -1940,7 +1940,6 @@ function updateSuspenseOffscreenState(
1940
1940
1941
1941
// TODO: Probably should inline this back
1942
1942
function shouldRemainOnFallback (
1943
- suspenseContext : SuspenseContext ,
1944
1943
current : null | Fiber ,
1945
1944
workInProgress : Fiber ,
1946
1945
renderLanes : Lanes ,
@@ -1960,7 +1959,8 @@ function shouldRemainOnFallback(
1960
1959
}
1961
1960
1962
1961
// Not currently showing content. Consult the Suspense context.
1963
- return hasSuspenseContext (
1962
+ const suspenseContext : SuspenseContext = suspenseStackCursor . current ;
1963
+ return hasSuspenseListContext (
1964
1964
suspenseContext ,
1965
1965
( ForceSuspenseFallback : SuspenseContext ) ,
1966
1966
) ;
@@ -1981,50 +1981,18 @@ function updateSuspenseComponent(current, workInProgress, renderLanes) {
1981
1981
}
1982
1982
}
1983
1983
1984
- let suspenseContext : SuspenseContext = suspenseStackCursor . current ;
1985
-
1986
1984
let showFallback = false ;
1987
1985
const didSuspend = ( workInProgress . flags & DidCapture ) !== NoFlags ;
1988
-
1989
1986
if (
1990
1987
didSuspend ||
1991
- shouldRemainOnFallback (
1992
- suspenseContext ,
1993
- current ,
1994
- workInProgress ,
1995
- renderLanes ,
1996
- )
1988
+ shouldRemainOnFallback ( current , workInProgress , renderLanes )
1997
1989
) {
1998
1990
// Something in this boundary's subtree already suspended. Switch to
1999
1991
// rendering the fallback children.
2000
1992
showFallback = true ;
2001
1993
workInProgress . flags &= ~ DidCapture ;
2002
- } else {
2003
- // Attempting the main content
2004
- if (
2005
- current === null ||
2006
- ( current . memoizedState : null | SuspenseState ) !== null
2007
- ) {
2008
- // This is a new mount or this boundary is already showing a fallback state.
2009
- // Mark this subtree context as having at least one invisible parent that could
2010
- // handle the fallback state.
2011
- // Avoided boundaries are not considered since they cannot handle preferred fallback states.
2012
- if (
2013
- ! enableSuspenseAvoidThisFallback ||
2014
- nextProps . unstable_avoidThisFallback !== true
2015
- ) {
2016
- suspenseContext = addSubtreeSuspenseContext (
2017
- suspenseContext ,
2018
- InvisibleParentSuspenseContext ,
2019
- ) ;
2020
- }
2021
- }
2022
1994
}
2023
1995
2024
- suspenseContext = setDefaultShallowSuspenseContext ( suspenseContext ) ;
2025
-
2026
- pushSuspenseContext ( workInProgress , suspenseContext ) ;
2027
-
2028
1996
// OK, the next part is confusing. We're about to reconcile the Suspense
2029
1997
// boundary's children. This involves some custom reconciliation logic. Two
2030
1998
// main reasons this is so complicated.
@@ -2052,24 +2020,40 @@ function updateSuspenseComponent(current, workInProgress, renderLanes) {
2052
2020
2053
2021
// Special path for hydration
2054
2022
// If we're currently hydrating, try to hydrate this boundary.
2055
- tryToClaimNextHydratableInstance ( workInProgress ) ;
2056
- // This could've been a dehydrated suspense component.
2057
- const suspenseState : null | SuspenseState = workInProgress . memoizedState ;
2058
- if ( suspenseState !== null ) {
2059
- const dehydrated = suspenseState . dehydrated ;
2060
- if ( dehydrated !== null ) {
2061
- return mountDehydratedSuspenseComponent (
2062
- workInProgress ,
2063
- dehydrated ,
2064
- renderLanes ,
2065
- ) ;
2023
+ if ( getIsHydrating ( ) ) {
2024
+ // We must push the suspense handler context *before* attempting to
2025
+ // hydrate, to avoid a mismatch in case it errors.
2026
+ if ( showFallback ) {
2027
+ pushPrimaryTreeSuspenseHandler ( workInProgress ) ;
2028
+ } else {
2029
+ pushFallbackTreeSuspenseHandler ( workInProgress ) ;
2030
+ }
2031
+ tryToClaimNextHydratableInstance ( workInProgress ) ;
2032
+ // This could've been a dehydrated suspense component.
2033
+ const suspenseState : null | SuspenseState = workInProgress . memoizedState ;
2034
+ if ( suspenseState !== null ) {
2035
+ const dehydrated = suspenseState . dehydrated ;
2036
+ if ( dehydrated !== null ) {
2037
+ return mountDehydratedSuspenseComponent (
2038
+ workInProgress ,
2039
+ dehydrated ,
2040
+ renderLanes ,
2041
+ ) ;
2042
+ }
2066
2043
}
2044
+ // If hydration didn't succeed, fall through to the normal Suspense path.
2045
+ // To avoid a stack mismatch we need to pop the Suspense handler that we
2046
+ // pushed above. This will become less awkward when move the hydration
2047
+ // logic to its own fiber.
2048
+ popSuspenseHandler ( workInProgress ) ;
2067
2049
}
2068
2050
2069
2051
const nextPrimaryChildren = nextProps . children ;
2070
2052
const nextFallbackChildren = nextProps . fallback ;
2071
2053
2072
2054
if ( showFallback ) {
2055
+ pushFallbackTreeSuspenseHandler ( workInProgress ) ;
2056
+
2073
2057
const fallbackFragment = mountSuspenseFallbackChildren (
2074
2058
workInProgress ,
2075
2059
nextPrimaryChildren ,
@@ -2099,6 +2083,7 @@ function updateSuspenseComponent(current, workInProgress, renderLanes) {
2099
2083
// This is a CPU-bound tree. Skip this tree and show a placeholder to
2100
2084
// unblock the surrounding content. Then immediately retry after the
2101
2085
// initial commit.
2086
+ pushFallbackTreeSuspenseHandler ( workInProgress ) ;
2102
2087
const fallbackFragment = mountSuspenseFallbackChildren (
2103
2088
workInProgress ,
2104
2089
nextPrimaryChildren ,
@@ -2122,6 +2107,7 @@ function updateSuspenseComponent(current, workInProgress, renderLanes) {
2122
2107
workInProgress . lanes = SomeRetryLane ;
2123
2108
return fallbackFragment ;
2124
2109
} else {
2110
+ pushPrimaryTreeSuspenseHandler ( workInProgress ) ;
2125
2111
return mountSuspensePrimaryChildren (
2126
2112
workInProgress ,
2127
2113
nextPrimaryChildren ,
@@ -2149,6 +2135,8 @@ function updateSuspenseComponent(current, workInProgress, renderLanes) {
2149
2135
}
2150
2136
2151
2137
if ( showFallback ) {
2138
+ pushFallbackTreeSuspenseHandler ( workInProgress ) ;
2139
+
2152
2140
const nextFallbackChildren = nextProps . fallback ;
2153
2141
const nextPrimaryChildren = nextProps . children ;
2154
2142
const fallbackChildFragment = updateSuspenseFallbackChildren (
@@ -2181,6 +2169,8 @@ function updateSuspenseComponent(current, workInProgress, renderLanes) {
2181
2169
workInProgress . memoizedState = SUSPENDED_MARKER ;
2182
2170
return fallbackChildFragment ;
2183
2171
} else {
2172
+ pushPrimaryTreeSuspenseHandler ( workInProgress ) ;
2173
+
2184
2174
const nextPrimaryChildren = nextProps . children ;
2185
2175
const primaryChildFragment = updateSuspensePrimaryChildren (
2186
2176
current ,
@@ -2551,6 +2541,7 @@ function updateDehydratedSuspenseComponent(
2551
2541
) : null | Fiber {
2552
2542
if ( ! didSuspend ) {
2553
2543
// This is the first render pass. Attempt to hydrate.
2544
+ pushPrimaryTreeSuspenseHandler ( workInProgress ) ;
2554
2545
2555
2546
// We should never be hydrating at this point because it is the first pass,
2556
2547
// but after we've already committed once.
@@ -2694,6 +2685,8 @@ function updateDehydratedSuspenseComponent(
2694
2685
2695
2686
if ( workInProgress . flags & ForceClientRender ) {
2696
2687
// Something errored during hydration. Try again without hydrating.
2688
+ pushPrimaryTreeSuspenseHandler ( workInProgress ) ;
2689
+
2697
2690
workInProgress . flags &= ~ ForceClientRender ;
2698
2691
return retrySuspenseComponentWithoutHydrating (
2699
2692
current ,
@@ -2707,6 +2700,10 @@ function updateDehydratedSuspenseComponent(
2707
2700
} else if ( ( workInProgress . memoizedState : null | SuspenseState ) !== null ) {
2708
2701
// Something suspended and we should still be in dehydrated mode.
2709
2702
// Leave the existing child in place.
2703
+
2704
+ // Push to avoid a mismatch
2705
+ pushFallbackTreeSuspenseHandler ( workInProgress ) ;
2706
+
2710
2707
workInProgress . child = current . child ;
2711
2708
// The dehydrated completion pass expects this flag to be there
2712
2709
// but the normal suspense pass doesn't.
@@ -2715,6 +2712,8 @@ function updateDehydratedSuspenseComponent(
2715
2712
} else {
2716
2713
// Suspended but we should no longer be in dehydrated mode.
2717
2714
// Therefore we now have to render the fallback.
2715
+ pushFallbackTreeSuspenseHandler ( workInProgress ) ;
2716
+
2718
2717
const nextPrimaryChildren = nextProps . children ;
2719
2718
const nextFallbackChildren = nextProps . fallback ;
2720
2719
const fallbackChildFragment = mountSuspenseFallbackAfterRetryWithoutHydrating (
@@ -3010,12 +3009,12 @@ function updateSuspenseListComponent(
3010
3009
3011
3010
let suspenseContext : SuspenseContext = suspenseStackCursor . current ;
3012
3011
3013
- const shouldForceFallback = hasSuspenseContext (
3012
+ const shouldForceFallback = hasSuspenseListContext (
3014
3013
suspenseContext ,
3015
3014
( ForceSuspenseFallback : SuspenseContext ) ,
3016
3015
) ;
3017
3016
if ( shouldForceFallback ) {
3018
- suspenseContext = setShallowSuspenseContext (
3017
+ suspenseContext = setShallowSuspenseListContext (
3019
3018
suspenseContext ,
3020
3019
ForceSuspenseFallback ,
3021
3020
) ;
@@ -3033,9 +3032,9 @@ function updateSuspenseListComponent(
3033
3032
renderLanes ,
3034
3033
) ;
3035
3034
}
3036
- suspenseContext = setDefaultShallowSuspenseContext ( suspenseContext ) ;
3035
+ suspenseContext = setDefaultShallowSuspenseListContext ( suspenseContext ) ;
3037
3036
}
3038
- pushSuspenseContext ( workInProgress , suspenseContext ) ;
3037
+ pushSuspenseListContext ( workInProgress , suspenseContext ) ;
3039
3038
3040
3039
if ( ( workInProgress . mode & ConcurrentMode ) === NoMode ) {
3041
3040
// In legacy mode, SuspenseList doesn't work so we just
@@ -3499,10 +3498,9 @@ function attemptEarlyBailoutIfNoScheduledUpdate(
3499
3498
const state : SuspenseState | null = workInProgress . memoizedState ;
3500
3499
if ( state !== null ) {
3501
3500
if ( state . dehydrated !== null ) {
3502
- pushSuspenseContext (
3503
- workInProgress ,
3504
- setDefaultShallowSuspenseContext ( suspenseStackCursor . current ) ,
3505
- ) ;
3501
+ // We're not going to render the children, so this is just to maintain
3502
+ // push/pop symmetry
3503
+ pushPrimaryTreeSuspenseHandler ( workInProgress ) ;
3506
3504
// We know that this component will suspend again because if it has
3507
3505
// been unsuspended it has committed as a resolved Suspense component.
3508
3506
// If it needs to be retried, it should have work scheduled on it.
@@ -3525,10 +3523,7 @@ function attemptEarlyBailoutIfNoScheduledUpdate(
3525
3523
} else {
3526
3524
// The primary child fragment does not have pending work marked
3527
3525
// on it
3528
- pushSuspenseContext (
3529
- workInProgress ,
3530
- setDefaultShallowSuspenseContext ( suspenseStackCursor . current ) ,
3531
- ) ;
3526
+ pushPrimaryTreeSuspenseHandler ( workInProgress ) ;
3532
3527
// The primary children do not have pending work with sufficient
3533
3528
// priority. Bailout.
3534
3529
const child = bailoutOnAlreadyFinishedWork (
@@ -3548,10 +3543,7 @@ function attemptEarlyBailoutIfNoScheduledUpdate(
3548
3543
}
3549
3544
}
3550
3545
} else {
3551
- pushSuspenseContext (
3552
- workInProgress ,
3553
- setDefaultShallowSuspenseContext ( suspenseStackCursor . current ) ,
3554
- ) ;
3546
+ pushPrimaryTreeSuspenseHandler ( workInProgress ) ;
3555
3547
}
3556
3548
break ;
3557
3549
}
@@ -3609,7 +3601,7 @@ function attemptEarlyBailoutIfNoScheduledUpdate(
3609
3601
renderState . tail = null ;
3610
3602
renderState . lastEffect = null ;
3611
3603
}
3612
- pushSuspenseContext ( workInProgress , suspenseStackCursor . current ) ;
3604
+ pushSuspenseListContext ( workInProgress , suspenseStackCursor . current ) ;
3613
3605
3614
3606
if ( hasChildWork ) {
3615
3607
break ;
0 commit comments