From f558fbce5d3157f9c88240c185a74c6ba3a3e56f Mon Sep 17 00:00:00 2001 From: Sebastian Sebbie Silbermann Date: Mon, 9 Feb 2026 23:56:45 +0100 Subject: [PATCH 1/2] Current behavior --- .../src/__tests__/store-test.js | 97 +++++++++++++++++++ 1 file changed, 97 insertions(+) diff --git a/packages/react-devtools-shared/src/__tests__/store-test.js b/packages/react-devtools-shared/src/__tests__/store-test.js index f3bcf189c8ca..0138ab8be785 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() { From 4cd0fb64f3f3e973e839fe75c052e6221748f1d6 Mon Sep 17 00:00:00 2001 From: Sebastian Sebbie Silbermann Date: Mon, 9 Feb 2026 23:58:36 +0100 Subject: [PATCH 2/2] [DevTools] Only block child Suspense boundaries if the parent has all shared suspenders removed --- .../src/__tests__/store-test.js | 2 +- .../src/backend/fiber/renderer.js | 19 ++++++++++--------- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/packages/react-devtools-shared/src/__tests__/store-test.js b/packages/react-devtools-shared/src/__tests__/store-test.js index 0138ab8be785..36198ac1e079 100644 --- a/packages/react-devtools-shared/src/__tests__/store-test.js +++ b/packages/react-devtools-shared/src/__tests__/store-test.js @@ -3686,7 +3686,7 @@ describe('Store', () => { [suspense-root] rects={null} - + `); // Now we remove all unique suspenders of the outer Suspense boundary. diff --git a/packages/react-devtools-shared/src/backend/fiber/renderer.js b/packages/react-devtools-shared/src/backend/fiber/renderer.js index 916d69823285..905524d0dd1b 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); + } } } }