Skip to content

Commit 090487c

Browse files
committed
Reconcile fallback children inside the parent's SuspenseNode
So that their suspends gets associated with the parent and not itself.
1 parent 37062ff commit 090487c

File tree

1 file changed

+96
-17
lines changed
  • packages/react-devtools-shared/src/backend/fiber

1 file changed

+96
-17
lines changed

packages/react-devtools-shared/src/backend/fiber/renderer.js

Lines changed: 96 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)