Skip to content

Commit 5502d85

Browse files
eps1lonhoxyq
andauthored
[DevTools] Unmount fallbacks in the context of the parent Suspense (#34475)
Co-authored-by: Ruslan Lesiutin <hoxy@meta.com>
1 parent 8a8e9a7 commit 5502d85

File tree

2 files changed

+60
-2
lines changed

2 files changed

+60
-2
lines changed

packages/react-devtools-shared/src/__tests__/store-test.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3079,6 +3079,10 @@ describe('Store', () => {
30793079
<Suspense name="head-fallback" rects={[{x:1,y:2,width:10,height:1}]}>
30803080
<Suspense name="main" rects={[{x:1,y:2,width:4,height:1}]}>
30813081
`);
3082+
3083+
await actAsync(() => render(null));
3084+
3085+
expect(store).toMatchInlineSnapshot(``);
30823086
});
30833087

30843088
it('should handle an empty root', async () => {

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

Lines changed: 56 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3070,6 +3070,24 @@ export function attach(
30703070
}
30713071
}
30723072
3073+
function unmountSuspenseChildrenRecursively(
3074+
contentInstance: DevToolsInstance,
3075+
stashedSuspenseParent: null | SuspenseNode,
3076+
stashedSuspensePrevious: null | SuspenseNode,
3077+
stashedSuspenseRemaining: null | SuspenseNode,
3078+
): void {
3079+
// First unmount only the Offscreen boundary. I.e. the main content.
3080+
unmountInstanceRecursively(contentInstance);
3081+
3082+
// Next, we'll pop back out of the SuspenseNode that we added above and now we'll
3083+
// unmount the fallback, unmounting anything in the context of the parent SuspenseNode.
3084+
// Since the fallback conceptually blocks the parent.
3085+
reconcilingParentSuspenseNode = stashedSuspenseParent;
3086+
previouslyReconciledSiblingSuspenseNode = stashedSuspensePrevious;
3087+
remainingReconcilingChildrenSuspenseNodes = stashedSuspenseRemaining;
3088+
unmountRemainingChildren();
3089+
}
3090+
30733091
function isChildOf(
30743092
parentInstance: DevToolsInstance,
30753093
childInstance: DevToolsInstance,
@@ -4015,6 +4033,7 @@ export function attach(
40154033
debug('unmountInstanceRecursively()', instance, reconcilingParent);
40164034
}
40174035
4036+
let shouldPopSuspenseNode = false;
40184037
const stashedParent = reconcilingParent;
40194038
const stashedPrevious = previouslyReconciledSibling;
40204039
const stashedRemaining = remainingReconcilingChildren;
@@ -4035,11 +4054,46 @@ export function attach(
40354054
previouslyReconciledSiblingSuspenseNode = null;
40364055
remainingReconcilingChildrenSuspenseNodes =
40374056
instance.suspenseNode.firstChild;
4057+
4058+
shouldPopSuspenseNode = true;
40384059
}
40394060
40404061
try {
40414062
// Unmount the remaining set.
4042-
unmountRemainingChildren();
4063+
if (
4064+
(instance.kind === FIBER_INSTANCE ||
4065+
instance.kind === FILTERED_FIBER_INSTANCE) &&
4066+
instance.data.tag === SuspenseComponent &&
4067+
OffscreenComponent !== -1
4068+
) {
4069+
const fiber = instance.data;
4070+
const contentFiberInstance = remainingReconcilingChildren;
4071+
const hydrated = isFiberHydrated(fiber);
4072+
if (hydrated) {
4073+
if (contentFiberInstance === null) {
4074+
throw new Error(
4075+
'There should always be an Offscreen Fiber child in a hydrated Suspense boundary.',
4076+
);
4077+
}
4078+
4079+
unmountSuspenseChildrenRecursively(
4080+
contentFiberInstance,
4081+
stashedSuspenseParent,
4082+
stashedSuspensePrevious,
4083+
stashedSuspenseRemaining,
4084+
);
4085+
// unmountSuspenseChildren already popped
4086+
shouldPopSuspenseNode = false;
4087+
} else {
4088+
if (contentFiberInstance !== null) {
4089+
throw new Error(
4090+
'A dehydrated Suspense node should not have a content Fiber.',
4091+
);
4092+
}
4093+
}
4094+
} else {
4095+
unmountRemainingChildren();
4096+
}
40434097
removePreviousSuspendedBy(
40444098
instance,
40454099
previousSuspendedBy,
@@ -4049,7 +4103,7 @@ export function attach(
40494103
reconcilingParent = stashedParent;
40504104
previouslyReconciledSibling = stashedPrevious;
40514105
remainingReconcilingChildren = stashedRemaining;
4052-
if (instance.suspenseNode !== null) {
4106+
if (shouldPopSuspenseNode) {
40534107
reconcilingParentSuspenseNode = stashedSuspenseParent;
40544108
previouslyReconciledSiblingSuspenseNode = stashedSuspensePrevious;
40554109
remainingReconcilingChildrenSuspenseNodes = stashedSuspenseRemaining;

0 commit comments

Comments
 (0)