[DevTools] Show name prop of Suspense / Activity in the Components Tree view (#34135)

The name prop will be used in the Suspense tab to help identity a
boundary. Activity will also allow names. A custom component can be
identified by the name of the component but built-ins doesn't have that.

This PR adds it to the Components Tree View as well since otherwise you
only have the key to go on. Normally we don't add all the props to avoid
making this view too noisy but this is an exception along with key to
help identify a boundary quickly in the tree.

Unlike the SuspenseNode store, this wouldn't ever have a name inferred
by owner since that kind of context already exists in this view.

<img width="600" height="161" alt="Screenshot 2025-08-08 at 1 20 36 PM"
src="https://github.com/user-attachments/assets/fe50d624-887a-4b9d-9186-75f131f83195"
/>

I also made both the key and name prop searchable.

<img width="608" height="206" alt="Screenshot 2025-08-08 at 1 32 27 PM"
src="https://github.com/user-attachments/assets/d3502d9c-7614-45fc-b973-57f06dd9cddc"
/>
This commit is contained in:
Sebastian Markbåge
2025-08-11 11:41:46 -04:00
committed by GitHub
parent b42a225d63
commit 0bd8e090aa
9 changed files with 56 additions and 2 deletions
@@ -2369,6 +2369,15 @@ export function attach(
const keyString = key === null ? null : String(key);
const keyStringID = getStringID(keyString);
const nameProp =
fiber.tag === SuspenseComponent
? fiber.memoizedProps.name
: fiber.tag === ActivityComponent
? fiber.memoizedProps.name
: null;
const namePropString = nameProp == null ? null : String(nameProp);
const namePropStringID = getStringID(namePropString);
pushOperation(TREE_OPERATION_ADD);
pushOperation(id);
pushOperation(elementType);
@@ -2376,6 +2385,7 @@ export function attach(
pushOperation(ownerID);
pushOperation(displayNameStringID);
pushOperation(keyStringID);
pushOperation(namePropStringID);
// If this subtree has a new mode, let the frontend know.
if ((fiber.mode & StrictModeBits) !== 0) {
@@ -2478,6 +2488,7 @@ export function attach(
// in such a way as to bypass the default stringification of the "key" property.
const keyString = key === null ? null : String(key);
const keyStringID = getStringID(keyString);
const namePropStringID = getStringID(null);
const id = instance.id;
@@ -2488,6 +2499,7 @@ export function attach(
pushOperation(ownerID);
pushOperation(displayNameStringID);
pushOperation(keyStringID);
pushOperation(namePropStringID);
const componentLogsEntry =
componentInfoToComponentLogsMap.get(componentInfo);
@@ -426,6 +426,7 @@ export function attach(
pushOperation(ownerID);
pushOperation(displayNameStringID);
pushOperation(keyStringID);
pushOperation(getStringID(null)); // name prop
}
}
+6
View File
@@ -1116,6 +1116,7 @@ export default class Store extends EventEmitter<{
isCollapsed: false, // Never collapse roots; it would hide the entire tree.
isStrictModeNonCompliant,
key: null,
nameProp: null,
ownerID: 0,
parentID: 0,
type,
@@ -1139,6 +1140,10 @@ export default class Store extends EventEmitter<{
const key = stringTable[keyStringID];
i++;
const namePropStringID = operations[i];
const nameProp = stringTable[namePropStringID];
i++;
if (__DEBUG__) {
debug(
'Add',
@@ -1180,6 +1185,7 @@ export default class Store extends EventEmitter<{
isCollapsed: this._collapseNodesByDefault,
isStrictModeNonCompliant: parentElement.isStrictModeNonCompliant,
key,
nameProp,
ownerID,
parentID,
type,
@@ -119,6 +119,7 @@ export default function Element({data, index, style}: Props): React.Node {
hocDisplayNames,
isStrictModeNonCompliant,
key,
nameProp,
compiledWithForget,
} = element;
const {
@@ -179,7 +180,24 @@ export default function Element({data, index, style}: Props): React.Node {
className={styles.KeyValue}
title={key}
onDoubleClick={handleKeyDoubleClick}>
<pre>{key}</pre>
<pre>
<IndexableDisplayName displayName={key} id={id} />
</pre>
</span>
"
</Fragment>
)}
{nameProp && (
<Fragment>
&nbsp;<span className={styles.KeyName}>name</span>="
<span
className={styles.KeyValue}
title={nameProp}
onDoubleClick={handleKeyDoubleClick}>
<pre>
<IndexableDisplayName displayName={nameProp} id={id} />
</pre>
</span>
"
</Fragment>
@@ -995,7 +995,14 @@ function recursivelySearchTree(
return;
}
const {children, displayName, hocDisplayNames, compiledWithForget} = element;
const {
children,
displayName,
hocDisplayNames,
compiledWithForget,
key,
nameProp,
} = element;
if (displayName != null && regExp.test(displayName) === true) {
searchResults.push(elementID);
} else if (
@@ -1006,6 +1013,10 @@ function recursivelySearchTree(
searchResults.push(elementID);
} else if (compiledWithForget && regExp.test('Forget')) {
searchResults.push(elementID);
} else if (typeof key === 'string' && regExp.test(key)) {
searchResults.push(elementID);
} else if (typeof nameProp === 'string' && regExp.test(nameProp)) {
searchResults.push(elementID);
}
children.forEach(childID =>
@@ -239,6 +239,9 @@ function updateTree(
const key = stringTable[keyStringID];
i++;
// skip name prop
i++;
if (__DEBUG__) {
debug(
'Add',
+1
View File
@@ -157,6 +157,7 @@ export type Element = {
type: ElementType,
displayName: string | null,
key: number | string | null,
nameProp: null | string,
hocDisplayNames: null | Array<string>,
+1
View File
@@ -271,6 +271,7 @@ export function printOperationsArray(operations: Array<number>) {
i++;
i++; // key
i++; // name
logs.push(
`Add node ${id} (${displayName || 'null'}) as child of ${parentID}`,
+1
View File
@@ -298,6 +298,7 @@ export type ViewTransitionProps = {
export type ActivityProps = {
mode?: 'hidden' | 'visible' | null | void,
children?: ReactNodeList,
name?: string,
};
export type SuspenseProps = {