diff --git a/packages/react-devtools-shared/src/__tests__/store-test.js b/packages/react-devtools-shared/src/__tests__/store-test.js
index f3bcf189c8ca..36198ac1e079 100644
--- a/packages/react-devtools-shared/src/__tests__/store-test.js
+++ b/packages/react-devtools-shared/src/__tests__/store-test.js
@@ -3617,6 +3617,103 @@ describe('Store', () => {
`);
});
+ // @reactVersion >= 17.0
+ it('continues to consider Suspense boundary as blocking if some child still is suspended on removed io', async () => {
+ function Component({promise}) {
+ readValue(promise);
+ return null;
+ }
+
+ let resolve;
+ const promise = new Promise(_resolve => {
+ resolve = _resolve;
+ });
+
+ await actAsync(() => {
+ render(
+
+
+
+
+
+
+ ,
+ );
+ });
+
+ expect(store).toMatchInlineSnapshot(`
+ [root]
+
+ [suspense-root] rects={null}
+
+ `);
+
+ await actAsync(() => {
+ resolve('Hello, World!');
+ });
+
+ expect(store).toMatchInlineSnapshot(`
+ [root]
+ ▾
+
+
+ ▾
+
+ [suspense-root] rects={null}
+
+
+ `);
+
+ // We remove one suspender.
+ // The inner one shouldn't have unique suspenders because it's still blocked
+ // by the outer one.
+ await actAsync(() => {
+ render(
+
+
+
+
+
+ ,
+ );
+ });
+
+ expect(store).toMatchInlineSnapshot(`
+ [root]
+ ▾
+
+ ▾
+
+ [suspense-root] rects={null}
+
+
+ `);
+
+ // Now we remove all unique suspenders of the outer Suspense boundary.
+ // The inner one is now independently revealed from the parent and should
+ // be marked as having unique suspenders.
+ // TODO: The outer boundary no longer has unique suspenders.
+ await actAsync(() => {
+ render(
+
+
+
+
+ ,
+ );
+ });
+
+ expect(store).toMatchInlineSnapshot(`
+ [root]
+ ▾
+ ▾
+
+ [suspense-root] rects={null}
+
+
+ `);
+ });
+
// @reactVersion >= 19
it('cleans up host hoistables', async () => {
function Left() {
diff --git a/packages/react-devtools-shared/src/backend/fiber/renderer.js b/packages/react-devtools-shared/src/backend/fiber/renderer.js
index 916d69823285..9b2022daacbd 100644
--- a/packages/react-devtools-shared/src/backend/fiber/renderer.js
+++ b/packages/react-devtools-shared/src/backend/fiber/renderer.js
@@ -3197,15 +3197,16 @@ export function attach(
environmentCounts.set(env, count - 1);
}
}
- }
- if (
- suspenseNode.hasUniqueSuspenders &&
- !ioExistsInSuspenseAncestor(suspenseNode, ioInfo)
- ) {
- // This entry wasn't in any ancestor and is no longer in this suspense boundary.
- // This means that a child might now be the unique suspender for this IO.
- // Search the child boundaries to see if we can reveal any of them.
- unblockSuspendedBy(suspenseNode, ioInfo);
+
+ if (
+ suspenseNode.hasUniqueSuspenders &&
+ !ioExistsInSuspenseAncestor(suspenseNode, ioInfo)
+ ) {
+ // This entry wasn't in any ancestor and is no longer in this suspense boundary.
+ // This means that a child might now be the unique suspender for this IO.
+ // Search the child boundaries to see if we can reveal any of them.
+ unblockSuspendedBy(suspenseNode, ioInfo);
+ }
}
}
}
@@ -6859,6 +6860,8 @@ export function attach(
// TODO Show custom UI for Cache like we do for Suspense
// For now, just hide state data entirely since it's not meant to be inspected.
+ // Make sure delete, rename, and override of state handles all tags for which
+ // we show state.
const showState =
tag === ClassComponent || tag === IncompleteClassComponent;
@@ -7815,8 +7818,13 @@ export function attach(
}
break;
case 'state':
- deletePathInObject(instance.state, path);
- instance.forceUpdate();
+ switch (fiber.tag) {
+ case ClassComponent:
+ case IncompleteClassComponent:
+ deletePathInObject(instance.state, path);
+ instance.forceUpdate();
+ break;
+ }
break;
}
}
@@ -7893,8 +7901,13 @@ export function attach(
}
break;
case 'state':
- renamePathInObject(instance.state, oldPath, newPath);
- instance.forceUpdate();
+ switch (fiber.tag) {
+ case ClassComponent:
+ case IncompleteClassComponent:
+ renamePathInObject(instance.state, oldPath, newPath);
+ instance.forceUpdate();
+ break;
+ }
break;
}
}
@@ -7964,6 +7977,7 @@ export function attach(
case 'state':
switch (fiber.tag) {
case ClassComponent:
+ case IncompleteClassComponent:
setInObject(instance.state, path, value);
instance.forceUpdate();
break;