Skip to content

Commit 57b79b0

Browse files
authored
[DevTools] Only block child Suspense boundaries if the parent has all shared suspenders removed (facebook#35737)
1 parent 70890e7 commit 57b79b0

File tree

2 files changed

+107
-9
lines changed

2 files changed

+107
-9
lines changed

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

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3617,6 +3617,103 @@ describe('Store', () => {
36173617
`);
36183618
});
36193619

3620+
// @reactVersion >= 17.0
3621+
it('continues to consider Suspense boundary as blocking if some child still is suspended on removed io', async () => {
3622+
function Component({promise}) {
3623+
readValue(promise);
3624+
return null;
3625+
}
3626+
3627+
let resolve;
3628+
const promise = new Promise(_resolve => {
3629+
resolve = _resolve;
3630+
});
3631+
3632+
await actAsync(() => {
3633+
render(
3634+
<React.Suspense fallback={null} name="outer">
3635+
<Component key="one" promise={promise} />
3636+
<Component key="two" promise={promise} />
3637+
<React.Suspense fallback={null} name="inner">
3638+
<Component key="three" promise={promise} />
3639+
</React.Suspense>
3640+
</React.Suspense>,
3641+
);
3642+
});
3643+
3644+
expect(store).toMatchInlineSnapshot(`
3645+
[root]
3646+
<Suspense name="outer">
3647+
[suspense-root] rects={null}
3648+
<Suspense name="outer" uniqueSuspenders={true} rects={null}>
3649+
`);
3650+
3651+
await actAsync(() => {
3652+
resolve('Hello, World!');
3653+
});
3654+
3655+
expect(store).toMatchInlineSnapshot(`
3656+
[root]
3657+
▾ <Suspense name="outer">
3658+
<Component key="one">
3659+
<Component key="two">
3660+
▾ <Suspense name="inner">
3661+
<Component key="three">
3662+
[suspense-root] rects={null}
3663+
<Suspense name="outer" uniqueSuspenders={true} rects={null}>
3664+
<Suspense name="inner" uniqueSuspenders={false} rects={null}>
3665+
`);
3666+
3667+
// We remove one suspender.
3668+
// The inner one shouldn't have unique suspenders because it's still blocked
3669+
// by the outer one.
3670+
await actAsync(() => {
3671+
render(
3672+
<React.Suspense fallback={null} name="outer">
3673+
<Component key="one" promise={promise} />
3674+
<React.Suspense fallback={null} name="inner">
3675+
<Component key="three" promise={promise} />
3676+
</React.Suspense>
3677+
</React.Suspense>,
3678+
);
3679+
});
3680+
3681+
expect(store).toMatchInlineSnapshot(`
3682+
[root]
3683+
▾ <Suspense name="outer">
3684+
<Component key="one">
3685+
▾ <Suspense name="inner">
3686+
<Component key="three">
3687+
[suspense-root] rects={null}
3688+
<Suspense name="outer" uniqueSuspenders={true} rects={null}>
3689+
<Suspense name="inner" uniqueSuspenders={false} rects={null}>
3690+
`);
3691+
3692+
// Now we remove all unique suspenders of the outer Suspense boundary.
3693+
// The inner one is now independently revealed from the parent and should
3694+
// be marked as having unique suspenders.
3695+
// TODO: The outer boundary no longer has unique suspenders.
3696+
await actAsync(() => {
3697+
render(
3698+
<React.Suspense fallback={null} name="outer">
3699+
<React.Suspense fallback={null} name="inner">
3700+
<Component key="three" promise={promise} />
3701+
</React.Suspense>
3702+
</React.Suspense>,
3703+
);
3704+
});
3705+
3706+
expect(store).toMatchInlineSnapshot(`
3707+
[root]
3708+
▾ <Suspense name="outer">
3709+
▾ <Suspense name="inner">
3710+
<Component key="three">
3711+
[suspense-root] rects={null}
3712+
<Suspense name="outer" uniqueSuspenders={true} rects={null}>
3713+
<Suspense name="inner" uniqueSuspenders={true} rects={null}>
3714+
`);
3715+
});
3716+
36203717
// @reactVersion >= 19
36213718
it('cleans up host hoistables', async () => {
36223719
function Left() {

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

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3197,15 +3197,16 @@ export function attach(
31973197
environmentCounts.set(env, count - 1);
31983198
}
31993199
}
3200-
}
3201-
if (
3202-
suspenseNode.hasUniqueSuspenders &&
3203-
!ioExistsInSuspenseAncestor(suspenseNode, ioInfo)
3204-
) {
3205-
// This entry wasn't in any ancestor and is no longer in this suspense boundary.
3206-
// This means that a child might now be the unique suspender for this IO.
3207-
// Search the child boundaries to see if we can reveal any of them.
3208-
unblockSuspendedBy(suspenseNode, ioInfo);
3200+
3201+
if (
3202+
suspenseNode.hasUniqueSuspenders &&
3203+
!ioExistsInSuspenseAncestor(suspenseNode, ioInfo)
3204+
) {
3205+
// This entry wasn't in any ancestor and is no longer in this suspense boundary.
3206+
// This means that a child might now be the unique suspender for this IO.
3207+
// Search the child boundaries to see if we can reveal any of them.
3208+
unblockSuspendedBy(suspenseNode, ioInfo);
3209+
}
32093210
}
32103211
}
32113212
}

0 commit comments

Comments
 (0)