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;