@samitouri / QOS-React-1 / commits / ab5238d5a4

[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" />

Sebastian Markbåge committed Aug 11, 2025 at 11:41 UTC ab5238d5a40a4a4a68b351d345ab26f8ec5785e2
9 files changed +56 -2
packages/react-devtools-shared/src/backend/fiber/renderer.js
+12
@@ -2369,6 +2369,15 @@ export function attach(
2369 const keyString = key === null ? null : String(key);
2370 const keyStringID = getStringID(keyString);
2371
2372 + const nameProp =
2373 + fiber.tag === SuspenseComponent
2374 + ? fiber.memoizedProps.name
2375 + : fiber.tag === ActivityComponent
2376 + ? fiber.memoizedProps.name
2377 + : null;
2378 + const namePropString = nameProp == null ? null : String(nameProp);
2379 + const namePropStringID = getStringID(namePropString);
2380 +
2381 pushOperation(TREE_OPERATION_ADD);
2382 pushOperation(id);
2383 pushOperation(elementType);
@@ -2376,6 +2385,7 @@ export function attach(
2385 pushOperation(ownerID);
2386 pushOperation(displayNameStringID);
2387 pushOperation(keyStringID);
2388 + pushOperation(namePropStringID);
2389
2390 // If this subtree has a new mode, let the frontend know.
2391 if ((fiber.mode & StrictModeBits) !== 0) {
@@ -2478,6 +2488,7 @@ export function attach(
2488 // in such a way as to bypass the default stringification of the "key" property.
2489 const keyString = key === null ? null : String(key);
2490 const keyStringID = getStringID(keyString);
2491 + const namePropStringID = getStringID(null);
2492
2493 const id = instance.id;
2494
@@ -2488,6 +2499,7 @@ export function attach(
2499 pushOperation(ownerID);
2500 pushOperation(displayNameStringID);
2501 pushOperation(keyStringID);
2502 + pushOperation(namePropStringID);
2503
2504 const componentLogsEntry =
2505 componentInfoToComponentLogsMap.get(componentInfo);
packages/react-devtools-shared/src/backend/legacy/renderer.js
+1
@@ -426,6 +426,7 @@ export function attach(
426 pushOperation(ownerID);
427 pushOperation(displayNameStringID);
428 pushOperation(keyStringID);
429 + pushOperation(getStringID(null)); // name prop
430 }
431 }
432
packages/react-devtools-shared/src/devtools/store.js
+6
@@ -1116,6 +1116,7 @@ export default class Store extends EventEmitter<{
1116 isCollapsed: false, // Never collapse roots; it would hide the entire tree.
1117 isStrictModeNonCompliant,
1118 key: null,
1119 + nameProp: null,
1120 ownerID: 0,
1121 parentID: 0,
1122 type,
@@ -1139,6 +1140,10 @@ export default class Store extends EventEmitter<{
1140 const key = stringTable[keyStringID];
1141 i++;
1142
1143 + const namePropStringID = operations[i];
1144 + const nameProp = stringTable[namePropStringID];
1145 + i++;
1146 +
1147 if (__DEBUG__) {
1148 debug(
1149 'Add',
@@ -1180,6 +1185,7 @@ export default class Store extends EventEmitter<{
1185 isCollapsed: this._collapseNodesByDefault,
1186 isStrictModeNonCompliant: parentElement.isStrictModeNonCompliant,
1187 key,
1188 + nameProp,
1189 ownerID,
1190 parentID,
1191 type,
packages/react-devtools-shared/src/devtools/views/Components/Element.js
+19 -1
@@ -119,6 +119,7 @@ export default function Element({data, index, style}: Props): React.Node {
119 hocDisplayNames,
120 isStrictModeNonCompliant,
121 key,
122 + nameProp,
123 compiledWithForget,
124 } = element;
125 const {
@@ -179,7 +180,24 @@ export default function Element({data, index, style}: Props): React.Node {
180 className={styles.KeyValue}
181 title={key}
182 onDoubleClick={handleKeyDoubleClick}>
182 - <pre>{key}</pre>
183 + <pre>
184 + <IndexableDisplayName displayName={key} id={id} />
185 + </pre>
186 + </span>
187 + "
188 + </Fragment>
189 + )}
190 +
191 + {nameProp && (
192 + <Fragment>
193 + &nbsp;<span className={styles.KeyName}>name</span>="
194 + <span
195 + className={styles.KeyValue}
196 + title={nameProp}
197 + onDoubleClick={handleKeyDoubleClick}>
198 + <pre>
199 + <IndexableDisplayName displayName={nameProp} id={id} />
200 + </pre>
201 </span>
202 "
203 </Fragment>
packages/react-devtools-shared/src/devtools/views/Components/TreeContext.js
+12 -1
@@ -995,7 +995,14 @@ function recursivelySearchTree(
995 return;
996 }
997
998 - const {children, displayName, hocDisplayNames, compiledWithForget} = element;
998 + const {
999 + children,
1000 + displayName,
1001 + hocDisplayNames,
1002 + compiledWithForget,
1003 + key,
1004 + nameProp,
1005 + } = element;
1006 if (displayName != null && regExp.test(displayName) === true) {
1007 searchResults.push(elementID);
1008 } else if (
@@ -1006,6 +1013,10 @@ function recursivelySearchTree(
1013 searchResults.push(elementID);
1014 } else if (compiledWithForget && regExp.test('Forget')) {
1015 searchResults.push(elementID);
1016 + } else if (typeof key === 'string' && regExp.test(key)) {
1017 + searchResults.push(elementID);
1018 + } else if (typeof nameProp === 'string' && regExp.test(nameProp)) {
1019 + searchResults.push(elementID);
1020 }
1021
1022 children.forEach(childID =>
packages/react-devtools-shared/src/devtools/views/Profiler/CommitTreeBuilder.js
+3
@@ -239,6 +239,9 @@ function updateTree(
239 const key = stringTable[keyStringID];
240 i++;
241
242 + // skip name prop
243 + i++;
244 +
245 if (__DEBUG__) {
246 debug(
247 'Add',
packages/react-devtools-shared/src/frontend/types.js
+1
@@ -157,6 +157,7 @@ export type Element = {
157 type: ElementType,
158 displayName: string | null,
159 key: number | string | null,
160 + nameProp: null | string,
161
162 hocDisplayNames: null | Array<string>,
163
packages/react-devtools-shared/src/utils.js
+1
@@ -271,6 +271,7 @@ export function printOperationsArray(operations: Array<number>) {
271 i++;
272
273 i++; // key
274 + i++; // name
275
276 logs.push(
277 `Add node ${id} (${displayName || 'null'}) as child of ${parentID}`,
packages/shared/ReactTypes.js
+1
@@ -298,6 +298,7 @@ export type ViewTransitionProps = {
298 export type ActivityProps = {
299 mode?: 'hidden' | 'visible' | null | void,
300 children?: ReactNodeList,
301 + name?: string,
302 };
303
304 export type SuspenseProps = {