Skip to content

Commit 5aec1b2

Browse files
authored
[DevTools] Attach async info in filtered fallback to parent of Suspense (#35456)
1 parent d6cae44 commit 5aec1b2

File tree

2 files changed

+78
-1
lines changed

2 files changed

+78
-1
lines changed

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

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3143,6 +3143,59 @@ describe('Store', () => {
31433143
expect(store).toMatchInlineSnapshot(``);
31443144
});
31453145

3146+
// @reactVersion >= 17.0
3147+
it('should track suspended by in filtered fallback', async () => {
3148+
function IgnoreMe({promise}) {
3149+
return readValue(promise);
3150+
}
3151+
3152+
function Component({promise}) {
3153+
return readValue(promise);
3154+
}
3155+
3156+
await actAsync(
3157+
async () =>
3158+
(store.componentFilters = [createDisplayNameFilter('^IgnoreMe', true)]),
3159+
);
3160+
3161+
let resolveFallback;
3162+
const fallbackPromise = new Promise(resolve => {
3163+
resolveFallback = resolve;
3164+
});
3165+
let resolveContent;
3166+
const contentPromise = new Promise(resolve => {
3167+
resolveContent = resolve;
3168+
});
3169+
3170+
await actAsync(() =>
3171+
render(
3172+
<React.Suspense
3173+
name="main"
3174+
fallback={<IgnoreMe promise={fallbackPromise} />}>
3175+
<Component promise={contentPromise} />
3176+
</React.Suspense>,
3177+
),
3178+
);
3179+
expect(store).toMatchInlineSnapshot(``);
3180+
3181+
await actAsync(() => resolveFallback('loading'));
3182+
expect(store).toMatchInlineSnapshot(`
3183+
[root]
3184+
<Suspense name="main">
3185+
[suspense-root] rects={null}
3186+
<Suspense name="main" rects={null}>
3187+
`);
3188+
3189+
await actAsync(() => resolveContent('content'));
3190+
expect(store).toMatchInlineSnapshot(`
3191+
[root]
3192+
▾ <Suspense name="main">
3193+
<Component>
3194+
[suspense-root] rects={null}
3195+
<Suspense name="main" rects={null}>
3196+
`);
3197+
});
3198+
31463199
// @reactVersion >= 19
31473200
it('should keep suspended boundaries in the Suspense tree but not hidden Activity', async () => {
31483201
const Activity = React.Activity || React.unstable_Activity;

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

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2992,6 +2992,30 @@ export function attach(
29922992
) {
29932993
parentInstance = parentInstance.parent;
29942994
}
2995+
if (parentInstance.kind === FIBER_INSTANCE) {
2996+
const fiber = parentInstance.data;
2997+
2998+
if (
2999+
fiber.tag === SuspenseComponent &&
3000+
parentInstance !== parentSuspenseNode.instance
3001+
) {
3002+
// We're about to attach async info to a Suspense boundary we're not
3003+
// actually considering the parent Suspense boundary for this async info.
3004+
// We must have not found a suitable Fiber inside the fallback (e.g. due to filtering).
3005+
// Use the parent of this instance instead since we treat async info
3006+
// attached to a Suspense boundary as that async info triggering the
3007+
// fallback of that boundary.
3008+
const parent = parentInstance.parent;
3009+
if (parent === null) {
3010+
// This shouldn't happen. Any <Suspense> would have at least have the
3011+
// host root as the parent which can't have a fallback.
3012+
throw new Error(
3013+
'Did not find a suitable instance for this async info. This is a bug in React.',
3014+
);
3015+
}
3016+
parentInstance = parent;
3017+
}
3018+
}
29953019
29963020
const suspenseNodeSuspendedBy = parentSuspenseNode.suspendedBy;
29973021
const ioInfo = asyncInfo.awaited;
@@ -5255,9 +5279,9 @@ export function attach(
52555279
// It might even result in a bad user experience for e.g. node selection in the Elements panel.
52565280
// The easiest fix is to strip out the intermediate Fragment fibers,
52575281
// so the Elements panel and Profiler don't need to special case them.
5258-
// Suspense components only have a non-null memoizedState if they're timed-out.
52595282
const isLegacySuspense =
52605283
nextFiber.tag === SuspenseComponent && OffscreenComponent === -1;
5284+
// Suspense components only have a non-null memoizedState if they're timed-out.
52615285
const prevDidTimeout =
52625286
isLegacySuspense && prevFiber.memoizedState !== null;
52635287
const nextDidTimeOut =

0 commit comments

Comments
 (0)