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. 
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