@@ -2454,11 +2454,6 @@ export function attach(
24542454 // the current parent here as well.
24552455 let reconcilingParentSuspenseNode: null | SuspenseNode = null;
24562456
2457- function isSuspenseInFallback(suspenseNode: SuspenseNode) {
2458- const fiber = suspenseNode.instance.data;
2459- return fiber.tag === SuspenseComponent && fiber.memoizedState !== null;
2460- }
2461-
24622457 function ioExistsInSuspenseAncestor(
24632458 suspenseNode: SuspenseNode,
24642459 ioInfo: ReactIOInfo,
@@ -2474,21 +2469,13 @@ export function attach(
24742469 }
24752470
24762471 function insertSuspendedBy(asyncInfo: ReactAsyncInfo): void {
2477- let parentSuspenseNode = reconcilingParentSuspenseNode;
2478- while (
2479- parentSuspenseNode !== null &&
2480- isSuspenseInFallback(parentSuspenseNode)
2481- ) {
2482- // If we have something that suspends inside the fallback tree of a Suspense boundary, then
2483- // we bubble that up to the nearest parent Suspense boundary that isn't in fallback mode.
2484- parentSuspenseNode = parentSuspenseNode.parent;
2485- }
2486- if (reconcilingParent === null || parentSuspenseNode === null) {
2472+ if (reconcilingParent === null || reconcilingParentSuspenseNode === null) {
24872473 throw new Error(
24882474 'It should not be possible to have suspended data outside the root. ' +
24892475 'Even suspending at the first position is still a child of the root.',
24902476 );
24912477 }
2478+ const parentSuspenseNode = reconcilingParentSuspenseNode;
24922479 // Use the nearest unfiltered parent so that there's always some component that has
24932480 // the entry on it even if you filter, or the root if all are filtered.
24942481 let parentInstance = reconcilingParent;
@@ -3095,10 +3082,12 @@ export function attach(
30953082 previouslyReconciledSibling = null;
30963083 remainingReconcilingChildren = null;
30973084 }
3085+ let shouldPopSuspenseNode = false;
30983086 if (newSuspenseNode !== null) {
30993087 reconcilingParentSuspenseNode = newSuspenseNode;
31003088 previouslyReconciledSiblingSuspenseNode = null;
31013089 remainingReconcilingChildrenSuspenseNodes = null;
3090+ shouldPopSuspenseNode = true;
31023091 }
31033092 try {
31043093 if (traceUpdatesEnabled) {
@@ -3176,6 +3165,44 @@ export function attach(
31763165 );
31773166 }
31783167 }
3168+ } else if (
3169+ fiber.tag === SuspenseComponent &&
3170+ OffscreenComponent !== -1 &&
3171+ newInstance !== null &&
3172+ newSuspenseNode !== null
3173+ ) {
3174+ // Modern Suspense path
3175+ const contentFiber = fiber.child;
3176+ if (contentFiber === null) {
3177+ throw new Error(
3178+ 'There should always be an Offscreen Fiber child in a Suspense boundary.',
3179+ );
3180+ }
3181+ const fallbackFiber = contentFiber.sibling;
3182+
3183+ // First update only the Offscreen boundary. I.e. the main content.
3184+ mountVirtualChildrenRecursively(
3185+ contentFiber,
3186+ fallbackFiber,
3187+ traceNearestHostComponentUpdate,
3188+ 0, // first level
3189+ );
3190+
3191+ // Next, we'll pop back out of the SuspenseNode that we added above and now we'll
3192+ // reconcile the fallback, reconciling anything by inserting into the parent SuspenseNode.
3193+ // Since the fallback conceptually blocks the parent.
3194+ reconcilingParentSuspenseNode = stashedSuspenseParent;
3195+ previouslyReconciledSiblingSuspenseNode = stashedSuspensePrevious;
3196+ remainingReconcilingChildrenSuspenseNodes = stashedSuspenseRemaining;
3197+ shouldPopSuspenseNode = false;
3198+ if (fallbackFiber !== null) {
3199+ mountVirtualChildrenRecursively(
3200+ fallbackFiber,
3201+ null,
3202+ traceNearestHostComponentUpdate,
3203+ 0, // first level
3204+ );
3205+ }
31793206 } else {
31803207 if (fiber.child !== null) {
31813208 mountChildrenRecursively(
@@ -3190,7 +3217,7 @@ export function attach(
31903217 previouslyReconciledSibling = stashedPrevious;
31913218 remainingReconcilingChildren = stashedRemaining;
31923219 }
3193- if (newSuspenseNode !== null ) {
3220+ if (shouldPopSuspenseNode ) {
31943221 reconcilingParentSuspenseNode = stashedSuspenseParent;
31953222 previouslyReconciledSiblingSuspenseNode = stashedSuspensePrevious;
31963223 remainingReconcilingChildrenSuspenseNodes = stashedSuspenseRemaining;
@@ -3814,6 +3841,7 @@ export function attach(
38143841 const stashedSuspenseParent = reconcilingParentSuspenseNode;
38153842 const stashedSuspensePrevious = previouslyReconciledSiblingSuspenseNode;
38163843 const stashedSuspenseRemaining = remainingReconcilingChildrenSuspenseNodes;
3844+ let shouldPopSuspenseNode = false;
38173845 let previousSuspendedBy = null;
38183846 if (fiberInstance !== null) {
38193847 previousSuspendedBy = fiberInstance.suspendedBy;
@@ -3843,6 +3871,7 @@ export function attach(
38433871 previouslyReconciledSiblingSuspenseNode = null;
38443872 remainingReconcilingChildrenSuspenseNodes = suspenseNode.firstChild;
38453873 suspenseNode.firstChild = null;
3874+ shouldPopSuspenseNode = true;
38463875 }
38473876 }
38483877 try {
@@ -3997,6 +4026,56 @@ export function attach(
39974026 // Children may have reordered while they were hidden.
39984027 shouldResetChildren = true;
39994028 }
4029+ } else if (
4030+ nextFiber.tag === SuspenseComponent &&
4031+ OffscreenComponent !== -1 &&
4032+ fiberInstance !== null &&
4033+ fiberInstance.suspenseNode !== null
4034+ ) {
4035+ // Modern Suspense path
4036+ const prevContentFiber = prevFiber.child;
4037+ const nextContentFiber = nextFiber.child;
4038+ if (nextContentFiber === null || prevContentFiber === null) {
4039+ throw new Error(
4040+ 'There should always be an Offscreen Fiber child in a Suspense boundary.',
4041+ );
4042+ }
4043+ const prevFallbackFiber = prevContentFiber.sibling;
4044+ const nextFallbackFiber = nextContentFiber.sibling;
4045+
4046+ // First update only the Offscreen boundary. I.e. the main content.
4047+ if (
4048+ updateVirtualChildrenRecursively(
4049+ nextContentFiber,
4050+ nextFallbackFiber,
4051+ prevContentFiber,
4052+ traceNearestHostComponentUpdate,
4053+ 0,
4054+ )
4055+ ) {
4056+ shouldResetChildren = true;
4057+ }
4058+
4059+ // Next, we'll pop back out of the SuspenseNode that we added above and now we'll
4060+ // reconcile the fallback, reconciling anything by inserting into the parent SuspenseNode.
4061+ // Since the fallback conceptually blocks the parent.
4062+ reconcilingParentSuspenseNode = stashedSuspenseParent;
4063+ previouslyReconciledSiblingSuspenseNode = stashedSuspensePrevious;
4064+ remainingReconcilingChildrenSuspenseNodes = stashedSuspenseRemaining;
4065+ shouldPopSuspenseNode = false;
4066+ if (nextFallbackFiber !== null) {
4067+ if (
4068+ updateVirtualChildrenRecursively(
4069+ nextFallbackFiber,
4070+ null,
4071+ prevFallbackFiber,
4072+ traceNearestHostComponentUpdate,
4073+ 0,
4074+ )
4075+ ) {
4076+ shouldResetChildren = true;
4077+ }
4078+ }
40004079 } else {
40014080 // Common case: Primary -> Primary.
40024081 // This is the same code path as for non-Suspense fibers.
@@ -4090,7 +4169,7 @@ export function attach(
40904169 reconcilingParent = stashedParent;
40914170 previouslyReconciledSibling = stashedPrevious;
40924171 remainingReconcilingChildren = stashedRemaining;
4093- if (fiberInstance.suspenseNode !== null ) {
4172+ if (shouldPopSuspenseNode ) {
40944173 reconcilingParentSuspenseNode = stashedSuspenseParent;
40954174 previouslyReconciledSiblingSuspenseNode = stashedSuspensePrevious;
40964175 remainingReconcilingChildrenSuspenseNodes = stashedSuspenseRemaining;
0 commit comments