@samitouri / QOS-React-2 / commits / f0c767e2a2

feat[devtools]: display native tag for host components for Native (#32762)

Native only. Displays the native tag for Native Host components inside a badge, when user inspects the component. Only displaying will be supported for now, because in order to get native tags indexable, they should be part of the bridge operations, which is technically a breaking change that requires significantly more time investment. The text will only be shown when user hovers over the badge. ![Screenshot 2025-03-26 at 19 46 40](https://github.com/user-attachments/assets/787530cf-c5e5-4b85-8e2a-15b006a3d783)

Ruslan Lesiutin committed Apr 2, 2025 at 22:44 UTC f0c767e2a26ec28d808c22f8af2e09f2e20cdcc2
9 files changed +97 -3
packages/react-devtools-shared/src/backend/fiber/renderer.js
+30
@@ -808,6 +808,27 @@ function getPublicInstance(instance: HostInstance): HostInstance {
808 return instance;
809 }
810
811 +function getNativeTag(instance: HostInstance): number | null {
812 + if (typeof instance !== 'object' || instance === null) {
813 + return null;
814 + }
815 +
816 + // Modern. Fabric.
817 + if (
818 + instance.canonical != null &&
819 + typeof instance.canonical.nativeTag === 'number'
820 + ) {
821 + return instance.canonical.nativeTag;
822 + }
823 +
824 + // Legacy. Paper.
825 + if (typeof instance._nativeTag === 'number') {
826 + return instance._nativeTag;
827 + }
828 +
829 + return null;
830 +}
831 +
832 function aquireHostInstance(
833 nearestInstance: DevToolsInstance,
834 hostInstance: HostInstance,
@@ -4298,6 +4319,11 @@ export function attach(
4319 componentLogsEntry = fiberToComponentLogsMap.get(fiber.alternate);
4320 }
4321
4322 + let nativeTag = null;
4323 + if (elementType === ElementTypeHostComponent) {
4324 + nativeTag = getNativeTag(fiber.stateNode);
4325 + }
4326 +
4327 return {
4328 id: fiberInstance.id,
4329
@@ -4364,6 +4390,8 @@ export function attach(
4390 rendererVersion: renderer.version,
4391
4392 plugins,
4393 +
4394 + nativeTag,
4395 };
4396 }
4397
@@ -4457,6 +4485,8 @@ export function attach(
4485 rendererVersion: renderer.version,
4486
4487 plugins,
4488 +
4489 + nativeTag: null,
4490 };
4491 }
4492
packages/react-devtools-shared/src/backend/legacy/renderer.js
+2
@@ -859,6 +859,8 @@ export function attach(
859 plugins: {
860 stylex: null,
861 },
862 +
863 + nativeTag: null,
864 };
865 }
866
packages/react-devtools-shared/src/backend/types.js
+3
@@ -294,6 +294,9 @@ export type InspectedElement = {
294
295 // UI plugins/visualizations for the inspected element.
296 plugins: Plugins,
297 +
298 + // React Native only.
299 + nativeTag: number | null,
300 };
301
302 export const InspectElementErrorType = 'error';
packages/react-devtools-shared/src/backendAPI.js
+2
@@ -239,6 +239,7 @@ export function convertInspectedElementBackendToFrontend(
239 key,
240 errors,
241 warnings,
242 + nativeTag,
243 } = inspectedElementBackend;
244
245 const inspectedElement: InspectedElementFrontend = {
@@ -273,6 +274,7 @@ export function convertInspectedElementBackendToFrontend(
274 state: hydrateHelper(state),
275 errors,
276 warnings,
277 + nativeTag,
278 };
279
280 return inspectedElement;
packages/react-devtools-shared/src/devtools/views/Components/InspectedElementBadges.js
+6 -1
@@ -11,21 +11,25 @@ import * as React from 'react';
11
12 import Badge from './Badge';
13 import ForgetBadge from './ForgetBadge';
14 +import NativeTagBadge from './NativeTagBadge';
15
16 import styles from './InspectedElementBadges.css';
17
18 type Props = {
19 hocDisplayNames: null | Array<string>,
20 compiledWithForget: boolean,
21 + nativeTag: number | null,
22 };
23
24 export default function InspectedElementBadges({
25 hocDisplayNames,
26 compiledWithForget,
27 + nativeTag,
28 }: Props): React.Node {
29 if (
30 !compiledWithForget &&
28 - (hocDisplayNames == null || hocDisplayNames.length === 0)
31 + (hocDisplayNames == null || hocDisplayNames.length === 0) &&
32 + nativeTag === null
33 ) {
34 return null;
35 }
@@ -33,6 +37,7 @@ export default function InspectedElementBadges({
37 return (
38 <div className={styles.Root}>
39 {compiledWithForget && <ForgetBadge indexable={false} />}
40 + {nativeTag !== null && <NativeTagBadge nativeTag={nativeTag} />}
41
42 {hocDisplayNames !== null &&
43 hocDisplayNames.map(hocDisplayName => (
packages/react-devtools-shared/src/devtools/views/Components/InspectedElementView.js
+9 -2
@@ -54,8 +54,14 @@ export default function InspectedElementView({
54 toggleParseHookNames,
55 symbolicatedSourcePromise,
56 }: Props): React.Node {
57 - const {owners, rendererPackageName, rendererVersion, rootType, source} =
58 - inspectedElement;
57 + const {
58 + owners,
59 + rendererPackageName,
60 + rendererVersion,
61 + rootType,
62 + source,
63 + nativeTag,
64 + } = inspectedElement;
65
66 const bridge = useContext(BridgeContext);
67 const store = useContext(StoreContext);
@@ -75,6 +81,7 @@ export default function InspectedElementView({
81 <InspectedElementBadges
82 hocDisplayNames={element.hocDisplayNames}
83 compiledWithForget={element.compiledWithForget}
84 + nativeTag={nativeTag}
85 />
86 </div>
87
packages/react-devtools-shared/src/devtools/views/Components/NativeTagBadge.css new
+11
@@ -0,0 +1,11 @@
1 +.Toggle {
2 + display: flex;
3 +}
4 +
5 +.Toggle > span { /* targets .ToggleContent */
6 + padding: 0;
7 +}
8 +
9 +.Badge {
10 + cursor: help;
11 +}
packages/react-devtools-shared/src/devtools/views/Components/NativeTagBadge.js new
+31
@@ -0,0 +1,31 @@
1 +/**
2 + * Copyright (c) Meta Platforms, Inc. and affiliates.
3 + *
4 + * This source code is licensed under the MIT license found in the
5 + * LICENSE file in the root directory of this source tree.
6 + *
7 + * @flow
8 + */
9 +
10 +import * as React from 'react';
11 +
12 +import Badge from './Badge';
13 +import Toggle from '../Toggle';
14 +
15 +import styles from './NativeTagBadge.css';
16 +
17 +type Props = {
18 + nativeTag: number,
19 +};
20 +
21 +const noop = () => {};
22 +const title =
23 + 'Unique identifier for the corresponding native component. React Native only.';
24 +
25 +export default function NativeTagBadge({nativeTag}: Props): React.Node {
26 + return (
27 + <Toggle onChange={noop} className={styles.Toggle} title={title}>
28 + <Badge className={styles.Badge}>Tag {nativeTag}</Badge>
29 + </Toggle>
30 + );
31 +}
packages/react-devtools-shared/src/frontend/types.js
+3
@@ -259,6 +259,9 @@ export type InspectedElement = {
259
260 // UI plugins/visualizations for the inspected element.
261 plugins: Plugins,
262 +
263 + // React Native only.
264 + nativeTag: number | null,
265 };
266
267 // TODO: Add profiling type