[DevTools] Use same Suspense naming heuristics when reconnecting (#34898)

This commit is contained in:
Sebastian "Sebbie" Silbermann 2025-10-18 12:54:05 +02:00 committed by GitHub
parent 3a669170e9
commit 40c7a7f6ca
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 64 additions and 16 deletions

View File

@ -3243,4 +3243,61 @@ describe('Store', () => {
<Suspense name="inner-suspense" rects={[{x:1,y:2,width:15,height:1}]}> <Suspense name="inner-suspense" rects={[{x:1,y:2,width:15,height:1}]}>
`); `);
}); });
// @reactVersion >= 19.0
it('guesses a Suspense name based on the owner', async () => {
let resolve;
const promise = new Promise(_resolve => {
resolve = _resolve;
});
function Inner() {
return (
<React.Suspense fallback={<p>Loading inner</p>}>
<p>{promise}</p>
</React.Suspense>
);
}
function Outer({children}) {
return (
<React.Suspense fallback={<p>Loading outer</p>}>
<p>{promise}</p>
{children}
</React.Suspense>
);
}
await actAsync(() => {
render(
<Outer>
<Inner />
</Outer>,
);
});
expect(store).toMatchInlineSnapshot(`
[root]
<Outer>
<Suspense>
[suspense-root] rects={[{x:1,y:2,width:13,height:1}]}
<Suspense name="Outer" rects={null}>
`);
console.log('...........................');
await actAsync(() => {
resolve('loaded');
});
expect(store).toMatchInlineSnapshot(`
[root]
<Outer>
<Suspense>
<Inner>
<Suspense>
[suspense-root] rects={[{x:1,y:2,width:6,height:1}, {x:1,y:2,width:6,height:1}]}
<Suspense name="Outer" rects={[{x:1,y:2,width:6,height:1}, {x:1,y:2,width:6,height:1}]}>
<Suspense name="Inner" rects={[{x:1,y:2,width:6,height:1}]}>
`);
});
}); });

View File

@ -2664,7 +2664,7 @@ export function attach(
const fiber = fiberInstance.data; const fiber = fiberInstance.data;
const props = fiber.memoizedProps; const props = fiber.memoizedProps;
// TODO: Compute a fallback name based on Owner, key etc. // The frontend will guess a name based on heuristics (e.g. owner) if no explicit name is given.
const name = const name =
fiber.tag !== SuspenseComponent || props === null fiber.tag !== SuspenseComponent || props === null
? null ? null

View File

@ -1646,10 +1646,6 @@ export default class Store extends EventEmitter<{
parentSuspense.children.push(id); parentSuspense.children.push(id);
} }
if (name === null) {
name = 'Unknown';
}
this._idToSuspense.set(id, { this._idToSuspense.set(id, {
id, id,
parentID, parentID,
@ -2170,13 +2166,12 @@ export default class Store extends EventEmitter<{
throw error; throw error;
} }
_guessSuspenseName(element: Element): string { _guessSuspenseName(element: Element): string | null {
const owner = this._idToElement.get(element.ownerID); const owner = this._idToElement.get(element.ownerID);
let name = 'Unknown';
if (owner !== undefined && owner.displayName !== null) { if (owner !== undefined && owner.displayName !== null) {
name = owner.displayName; return owner.displayName;
} }
return name; return null;
} }
} }

View File

@ -63,11 +63,7 @@ function printRects(rects: SuspenseNode['rects']): string {
} }
function printSuspense(suspense: SuspenseNode): string { function printSuspense(suspense: SuspenseNode): string {
let name = ''; const name = ` name="${suspense.name || 'Unknown'}"`;
if (suspense.name !== null) {
name = ` name="${suspense.name}"`;
}
const printedRects = printRects(suspense.rects); const printedRects = printRects(suspense.rects);
return `<Suspense${name}${printedRects}>`; return `<Suspense${name}${printedRects}>`;

View File

@ -72,7 +72,7 @@ export default function SuspenseBreadcrumbs(): React$Node {
className={styles.SuspenseBreadcrumbsButton} className={styles.SuspenseBreadcrumbsButton}
onClick={handleClick.bind(null, id)} onClick={handleClick.bind(null, id)}
type="button"> type="button">
{node === null ? 'Unknown' : node.name} {node === null ? 'Unknown' : node.name || 'Unknown'}
</button> </button>
</li> </li>
); );

View File

@ -196,7 +196,7 @@ function SuspenseRects({
onPointerOver={handlePointerOver} onPointerOver={handlePointerOver}
onPointerLeave={handlePointerLeave} onPointerLeave={handlePointerLeave}
// Reach-UI tooltip will go out of bounds of parent scroll container. // Reach-UI tooltip will go out of bounds of parent scroll container.
title={suspense.name} title={suspense.name || 'Unknown'}
/> />
); );
})} })}