37
import type {RootState} from './ReactFiberRoot.new';
38
import type {TracingMarkerInstance} from './ReactFiberTracingMarkerComponent.new';
39
import {
40
- enableSuspenseAvoidThisFallback,
40
enableCPUSuspense,
41
enableUseMutableSource,
42
} from 'shared/ReactFeatureFlags';
166
import {pushHostContext, pushHostContainer} from './ReactFiberHostContext.new';
167
import {
168
suspenseStackCursor,
170
- pushSuspenseContext,
171
- InvisibleParentSuspenseContext,
169
+ pushSuspenseListContext,
170
ForceSuspenseFallback,
173
- hasSuspenseContext,
174
- setDefaultShallowSuspenseContext,
175
- addSubtreeSuspenseContext,
176
- setShallowSuspenseContext,
171
+ hasSuspenseListContext,
172
+ setDefaultShallowSuspenseListContext,
173
+ setShallowSuspenseListContext,
174
+ pushPrimaryTreeSuspenseHandler,
175
+ pushFallbackTreeSuspenseHandler,
176
+ popSuspenseHandler,
177
} from './ReactFiberSuspenseContext.new';
178
+import {
179
+ pushHiddenContext,
180
+ reuseHiddenContextOnStack,
181
+} from './ReactFiberHiddenContext.new';
182
import {findFirstSuspended} from './ReactFiberSuspenseComponent.new';
183
import {
184
pushProvider,
236
renderDidSuspendDelayIfPossible,
237
markSkippedUpdateLanes,
238
getWorkInProgressRoot,
235
- pushRenderLanes,
239
} from './ReactFiberWorkLoop.new';
240
import {enqueueConcurrentRenderForLane} from './ReactFiberConcurrentUpdates.new';
241
import {setWorkInProgressVersion} from './ReactMutableSource.new';
691
pushTransition(workInProgress, null, null);
692
}
693
}
691
- pushRenderLanes(workInProgress, renderLanes);
694
+ reuseHiddenContextOnStack(workInProgress);
695
} else if (!includesSomeLane(renderLanes, (OffscreenLane: Lane))) {
693
- let spawnedCachePool: SpawnedCachePool | null = null;
696
// We're hidden, and we're not rendering at Offscreen. We will bail out
697
// and resume this tree later.
696
- let nextBaseLanes;
698
+ let nextBaseLanes = renderLanes;
699
if (prevState !== null) {
698
- const prevBaseLanes = prevState.baseLanes;
699
- nextBaseLanes = mergeLanes(prevBaseLanes, renderLanes);
700
- if (enableCache) {
701
- // Save the cache pool so we can resume later.
702
- spawnedCachePool = getOffscreenDeferredCache();
703
- }
704
- } else {
705
- nextBaseLanes = renderLanes;
700
+ // Include the base lanes from the last render
701
+ nextBaseLanes = mergeLanes(nextBaseLanes, prevState.baseLanes);
702
}
703
704
// Schedule this fiber to re-render at offscreen priority. Then bailout.
707
);
708
const nextState: OffscreenState = {
709
baseLanes: nextBaseLanes,
714
- cachePool: spawnedCachePool,
710
+ // Save the cache pool so we can resume later.
711
+ cachePool: enableCache ? getOffscreenDeferredCache() : null,
712
};
713
workInProgress.memoizedState = nextState;
714
workInProgress.updateQueue = null;
722
723
// We're about to bail out, but we need to push this to the stack anyway
724
// to avoid a push/pop misalignment.
728
- pushRenderLanes(workInProgress, nextBaseLanes);
725
+ reuseHiddenContextOnStack(workInProgress);
726
727
if (enableLazyContextPropagation && current !== null) {
728
// Since this tree will resume rendering in a separate render, we need
746
cachePool: null,
747
};
748
workInProgress.memoizedState = nextState;
752
- // Push the lanes that were skipped when we bailed out.
753
- const subtreeRenderLanes =
754
- prevState !== null ? prevState.baseLanes : renderLanes;
749
if (enableCache && current !== null) {
750
// If the render that spawned this one accessed the cache pool, resume
751
// using the same cache. Unless the parent changed, since that means
756
pushTransition(workInProgress, prevCachePool, null);
757
}
758
765
- pushRenderLanes(workInProgress, subtreeRenderLanes);
759
+ // Push the lanes that were skipped when we bailed out.
760
+ if (prevState !== null) {
761
+ pushHiddenContext(workInProgress, prevState);
762
+ } else {
763
+ reuseHiddenContextOnStack(workInProgress);
764
+ }
765
}
766
} else {
767
// Rendering a visible tree.
769
- let subtreeRenderLanes;
768
if (prevState !== null) {
769
// We're going from hidden -> visible.
772
-
773
- subtreeRenderLanes = mergeLanes(prevState.baseLanes, renderLanes);
774
-
770
let prevCachePool = null;
771
if (enableCache) {
772
// If the render that spawned this one accessed the cache pool, resume
784
785
pushTransition(workInProgress, prevCachePool, transitions);
786
787
+ // Push the lanes that were skipped when we bailed out.
788
+ pushHiddenContext(workInProgress, prevState);
789
+
790
// Since we're not hidden anymore, reset the state
791
workInProgress.memoizedState = null;
792
} else {
793
// We weren't previously hidden, and we still aren't, so there's nothing
794
// special to do. Need to push to the stack regardless, though, to avoid
795
// a push/pop misalignment.
798
- subtreeRenderLanes = renderLanes;
796
797
if (enableCache) {
798
// If the render that spawned this one accessed the cache pool, resume
802
pushTransition(workInProgress, null, null);
803
}
804
}
805
+
806
+ // We're about to bail out, but we need to push this to the stack anyway
807
+ // to avoid a push/pop misalignment.
808
+ reuseHiddenContextOnStack(workInProgress);
809
}
809
- pushRenderLanes(workInProgress, subtreeRenderLanes);
810
}
811
812
reconcileChildren(current, workInProgress, nextChildren, renderLanes);
1969
1970
// TODO: Probably should inline this back
1971
function shouldRemainOnFallback(
1972
- suspenseContext: SuspenseContext,
1972
current: null | Fiber,
1973
workInProgress: Fiber,
1974
renderLanes: Lanes,
1988
}
1989
1990
// Not currently showing content. Consult the Suspense context.
1992
- return hasSuspenseContext(
1991
+ const suspenseContext: SuspenseContext = suspenseStackCursor.current;
1992
+ return hasSuspenseListContext(
1993
suspenseContext,
1994
(ForceSuspenseFallback: SuspenseContext),
1995
);
2010
}
2011
}
2012
2013
- let suspenseContext: SuspenseContext = suspenseStackCursor.current;
2014
-
2013
let showFallback = false;
2014
const didSuspend = (workInProgress.flags & DidCapture) !== NoFlags;
2017
-
2015
if (
2016
didSuspend ||
2020
- shouldRemainOnFallback(
2021
- suspenseContext,
2022
- current,
2023
- workInProgress,
2024
- renderLanes,
2025
- )
2017
+ shouldRemainOnFallback(current, workInProgress, renderLanes)
2018
) {
2019
// Something in this boundary's subtree already suspended. Switch to
2020
// rendering the fallback children.
2021
showFallback = true;
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
- }
2023
}
2024
2053
- suspenseContext = setDefaultShallowSuspenseContext(suspenseContext);
2054
-
2055
- pushSuspenseContext(workInProgress, suspenseContext);
2056
-
2025
// OK, the next part is confusing. We're about to reconcile the Suspense
2026
// boundary's children. This involves some custom reconciliation logic. Two
2027
// main reasons this is so complicated.
2049
2050
// Special path for hydration
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
+ }
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);
2078
}
2079
2080
const nextPrimaryChildren = nextProps.children;
2081
const nextFallbackChildren = nextProps.fallback;
2082
2083
if (showFallback) {
2084
+ pushFallbackTreeSuspenseHandler(workInProgress);
2085
+
2086
const fallbackFragment = mountSuspenseFallbackChildren(
2087
workInProgress,
2088
nextPrimaryChildren,
2115
// This is a CPU-bound tree. Skip this tree and show a placeholder to
2116
// unblock the surrounding content. Then immediately retry after the
2117
// initial commit.
2118
+ pushFallbackTreeSuspenseHandler(workInProgress);
2119
const fallbackFragment = mountSuspenseFallbackChildren(
2120
workInProgress,
2121
nextPrimaryChildren,
2139
workInProgress.lanes = SomeRetryLane;
2140
return fallbackFragment;
2141
} else {
2142
+ pushPrimaryTreeSuspenseHandler(workInProgress);
2143
return mountSuspensePrimaryChildren(
2144
workInProgress,
2145
nextPrimaryChildren,
2167
}
2168
2169
if (showFallback) {
2170
+ pushFallbackTreeSuspenseHandler(workInProgress);
2171
+
2172
const nextFallbackChildren = nextProps.fallback;
2173
const nextPrimaryChildren = nextProps.children;
2174
const fallbackChildFragment = updateSuspenseFallbackChildren(
2203
workInProgress.memoizedState = SUSPENDED_MARKER;
2204
return fallbackChildFragment;
2205
} else {
2206
+ pushPrimaryTreeSuspenseHandler(workInProgress);
2207
+
2208
const nextPrimaryChildren = nextProps.children;
2209
const primaryChildFragment = updateSuspensePrimaryChildren(
2210
current,
2575
): null | Fiber {
2576
if (!didSuspend) {
2577
// This is the first render pass. Attempt to hydrate.
2578
+ pushPrimaryTreeSuspenseHandler(workInProgress);
2579
2580
// We should never be hydrating at this point because it is the first pass,
2581
// but after we've already committed once.
2742
2743
if (workInProgress.flags & ForceClientRender) {
2744
// Something errored during hydration. Try again without hydrating.
2745
+ pushPrimaryTreeSuspenseHandler(workInProgress);
2746
+
2747
workInProgress.flags &= ~ForceClientRender;
2748
const capturedValue = createCapturedValue(
2749
new Error(
2760
} else if ((workInProgress.memoizedState: null | SuspenseState) !== null) {
2761
// Something suspended and we should still be in dehydrated mode.
2762
// Leave the existing child in place.
2763
+
2764
+ // Push to avoid a mismatch
2765
+ pushFallbackTreeSuspenseHandler(workInProgress);
2766
+
2767
workInProgress.child = current.child;
2768
// The dehydrated completion pass expects this flag to be there
2769
// but the normal suspense pass doesn't.
2772
} else {
2773
// Suspended but we should no longer be in dehydrated mode.
2774
// Therefore we now have to render the fallback.
2775
+ pushFallbackTreeSuspenseHandler(workInProgress);
2776
+
2777
const nextPrimaryChildren = nextProps.children;
2778
const nextFallbackChildren = nextProps.fallback;
2779
const fallbackChildFragment = mountSuspenseFallbackAfterRetryWithoutHydrating(
3069
3070
let suspenseContext: SuspenseContext = suspenseStackCursor.current;
3071
3073
- const shouldForceFallback = hasSuspenseContext(
3072
+ const shouldForceFallback = hasSuspenseListContext(
3073
suspenseContext,
3074
(ForceSuspenseFallback: SuspenseContext),
3075
);
3076
if (shouldForceFallback) {
3078
- suspenseContext = setShallowSuspenseContext(
3077
+ suspenseContext = setShallowSuspenseListContext(
3078
suspenseContext,
3079
ForceSuspenseFallback,
3080
);
3092
renderLanes,
3093
);
3094
}
3096
- suspenseContext = setDefaultShallowSuspenseContext(suspenseContext);
3095
+ suspenseContext = setDefaultShallowSuspenseListContext(suspenseContext);
3096
}
3098
- pushSuspenseContext(workInProgress, suspenseContext);
3097
+ pushSuspenseListContext(workInProgress, suspenseContext);
3098
3099
if ((workInProgress.mode & ConcurrentMode) === NoMode) {
3100
// In legacy mode, SuspenseList doesn't work so we just
3558
const state: SuspenseState | null = workInProgress.memoizedState;
3559
if (state !== null) {
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);
3564
// We know that this component will suspend again because if it has
3565
// been unsuspended it has committed as a resolved Suspense component.
3566
// If it needs to be retried, it should have work scheduled on it.
3583
} else {
3584
// The primary child fragment does not have pending work marked
3585
// on it
3588
- pushSuspenseContext(
3589
- workInProgress,
3590
- setDefaultShallowSuspenseContext(suspenseStackCursor.current),
3591
- );
3586
+ pushPrimaryTreeSuspenseHandler(workInProgress);
3587
// The primary children do not have pending work with sufficient
3588
// priority. Bailout.
3589
const child = bailoutOnAlreadyFinishedWork(
3603
}
3604
}
3605
} else {
3611
- pushSuspenseContext(
3612
- workInProgress,
3613
- setDefaultShallowSuspenseContext(suspenseStackCursor.current),
3614
- );
3606
+ pushPrimaryTreeSuspenseHandler(workInProgress);
3607
}
3608
break;
3609
}
3661
renderState.tail = null;
3662
renderState.lastEffect = null;
3663
}
3672
- pushSuspenseContext(workInProgress, suspenseStackCursor.current);
3664
+ pushSuspenseListContext(workInProgress, suspenseStackCursor.current);
3665
3666
if (hasChildWork) {
3667
break;