[DevTools] Use same Suspense naming heuristics when reconnecting (#34898)
Sebastian "Sebbie" Silbermann committed
Oct 18, 2025 at 12:54 UTC
40c7a7f6cac62bd6721328524cc787475ce026eb
6 files changed
+64
-16
packages/react-devtools-shared/src/__tests__/store-test.js
+57
@@ -3243,4 +3243,61 @@ describe('Store', () => {
3243
<Suspense name="inner-suspense" rects={[{x:1,y:2,width:15,height:1}]}>
3244
`);
3245
});
3246
+
3247
+ // @reactVersion >= 19.0
3248
+ it('guesses a Suspense name based on the owner', async () => {
3249
+ let resolve;
3250
+ const promise = new Promise(_resolve => {
3251
+ resolve = _resolve;
3252
+ });
3253
+ function Inner() {
3254
+ return (
3255
+ <React.Suspense fallback={<p>Loading inner</p>}>
3256
+ <p>{promise}</p>
3257
+ </React.Suspense>
3258
+ );
3259
+ }
3260
+
3261
+ function Outer({children}) {
3262
+ return (
3263
+ <React.Suspense fallback={<p>Loading outer</p>}>
3264
+ <p>{promise}</p>
3265
+ {children}
3266
+ </React.Suspense>
3267
+ );
3268
+ }
3269
+
3270
+ await actAsync(() => {
3271
+ render(
3272
+ <Outer>
3273
+ <Inner />
3274
+ </Outer>,
3275
+ );
3276
+ });
3277
+
3278
+ expect(store).toMatchInlineSnapshot(`
3279
+ [root]
3280
+ ▾ <Outer>
3281
+ <Suspense>
3282
+ [suspense-root] rects={[{x:1,y:2,width:13,height:1}]}
3283
+ <Suspense name="Outer" rects={null}>
3284
+ `);
3285
+
3286
+ console.log('...........................');
3287
+
3288
+ await actAsync(() => {
3289
+ resolve('loaded');
3290
+ });
3291
+
3292
+ expect(store).toMatchInlineSnapshot(`
3293
+ [root]
3294
+ ▾ <Outer>
3295
+ ▾ <Suspense>
3296
+ ▾ <Inner>
3297
+ <Suspense>
3298
+ [suspense-root] rects={[{x:1,y:2,width:6,height:1}, {x:1,y:2,width:6,height:1}]}
3299
+ <Suspense name="Outer" rects={[{x:1,y:2,width:6,height:1}, {x:1,y:2,width:6,height:1}]}>
3300
+ <Suspense name="Inner" rects={[{x:1,y:2,width:6,height:1}]}>
3301
+ `);
3302
+ });
3303
});
packages/react-devtools-shared/src/backend/fiber/renderer.js
+1
-1
@@ -2664,7 +2664,7 @@ export function attach(
2664
2665
const fiber = fiberInstance.data;
2666
const props = fiber.memoizedProps;
2667
- // TODO: Compute a fallback name based on Owner, key etc.
2667
+ // The frontend will guess a name based on heuristics (e.g. owner) if no explicit name is given.
2668
const name =
2669
fiber.tag !== SuspenseComponent || props === null
2670
? null
packages/react-devtools-shared/src/devtools/store.js
+3
-8
@@ -1646,10 +1646,6 @@ export default class Store extends EventEmitter<{
1646
parentSuspense.children.push(id);
1647
}
1648
1649
- if (name === null) {
1650
- name = 'Unknown';
1651
- }
1652
-
1649
this._idToSuspense.set(id, {
1650
id,
1651
parentID,
@@ -2170,13 +2166,12 @@ export default class Store extends EventEmitter<{
2166
throw error;
2167
}
2168
2173
- _guessSuspenseName(element: Element): string {
2169
+ _guessSuspenseName(element: Element): string | null {
2170
const owner = this._idToElement.get(element.ownerID);
2175
- let name = 'Unknown';
2171
if (owner !== undefined && owner.displayName !== null) {
2177
- name = owner.displayName;
2172
+ return owner.displayName;
2173
}
2174
2180
- return name;
2175
+ return null;
2176
}
2177
}
packages/react-devtools-shared/src/devtools/utils.js
+1
-5
@@ -63,11 +63,7 @@ function printRects(rects: SuspenseNode['rects']): string {
63
}
64
65
function printSuspense(suspense: SuspenseNode): string {
66
- let name = '';
67
- if (suspense.name !== null) {
68
- name = ` name="${suspense.name}"`;
69
- }
70
-
66
+ const name = ` name="${suspense.name || 'Unknown'}"`;
67
const printedRects = printRects(suspense.rects);
68
69
return `<Suspense${name}${printedRects}>`;
packages/react-devtools-shared/src/devtools/views/SuspenseTab/SuspenseBreadcrumbs.js
+1
-1
@@ -72,7 +72,7 @@ export default function SuspenseBreadcrumbs(): React$Node {
72
className={styles.SuspenseBreadcrumbsButton}
73
onClick={handleClick.bind(null, id)}
74
type="button">
75
- {node === null ? 'Unknown' : node.name}
75
+ {node === null ? 'Unknown' : node.name || 'Unknown'}
76
</button>
77
</li>
78
);
packages/react-devtools-shared/src/devtools/views/SuspenseTab/SuspenseRects.js
+1
-1
@@ -196,7 +196,7 @@ function SuspenseRects({
196
onPointerOver={handlePointerOver}
197
onPointerLeave={handlePointerLeave}
198
// Reach-UI tooltip will go out of bounds of parent scroll container.
199
- title={suspense.name}
199
+ title={suspense.name || 'Unknown'}
200
/>
201
);
202
})}