Skip to content

Commit f0c767e

Browse files
authored
feat[devtools]: display native tag for host components for Native (facebook#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)
1 parent b2f6365 commit f0c767e

File tree

9 files changed

+97
-3
lines changed

9 files changed

+97
-3
lines changed

packages/react-devtools-shared/src/backend/fiber/renderer.js

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -808,6 +808,27 @@ function getPublicInstance(instance: HostInstance): HostInstance {
808808
return instance;
809809
}
810810

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+
811832
function aquireHostInstance(
812833
nearestInstance: DevToolsInstance,
813834
hostInstance: HostInstance,
@@ -4298,6 +4319,11 @@ export function attach(
42984319
componentLogsEntry = fiberToComponentLogsMap.get(fiber.alternate);
42994320
}
43004321

4322+
let nativeTag = null;
4323+
if (elementType === ElementTypeHostComponent) {
4324+
nativeTag = getNativeTag(fiber.stateNode);
4325+
}
4326+
43014327
return {
43024328
id: fiberInstance.id,
43034329

@@ -4364,6 +4390,8 @@ export function attach(
43644390
rendererVersion: renderer.version,
43654391

43664392
plugins,
4393+
4394+
nativeTag,
43674395
};
43684396
}
43694397

@@ -4457,6 +4485,8 @@ export function attach(
44574485
rendererVersion: renderer.version,
44584486

44594487
plugins,
4488+
4489+
nativeTag: null,
44604490
};
44614491
}
44624492

packages/react-devtools-shared/src/backend/legacy/renderer.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -859,6 +859,8 @@ export function attach(
859859
plugins: {
860860
stylex: null,
861861
},
862+
863+
nativeTag: null,
862864
};
863865
}
864866

packages/react-devtools-shared/src/backend/types.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -294,6 +294,9 @@ export type InspectedElement = {
294294

295295
// UI plugins/visualizations for the inspected element.
296296
plugins: Plugins,
297+
298+
// React Native only.
299+
nativeTag: number | null,
297300
};
298301

299302
export const InspectElementErrorType = 'error';

packages/react-devtools-shared/src/backendAPI.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,7 @@ export function convertInspectedElementBackendToFrontend(
239239
key,
240240
errors,
241241
warnings,
242+
nativeTag,
242243
} = inspectedElementBackend;
243244

244245
const inspectedElement: InspectedElementFrontend = {
@@ -273,6 +274,7 @@ export function convertInspectedElementBackendToFrontend(
273274
state: hydrateHelper(state),
274275
errors,
275276
warnings,
277+
nativeTag,
276278
};
277279

278280
return inspectedElement;

packages/react-devtools-shared/src/devtools/views/Components/InspectedElementBadges.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,28 +11,33 @@ import * as React from 'react';
1111

1212
import Badge from './Badge';
1313
import ForgetBadge from './ForgetBadge';
14+
import NativeTagBadge from './NativeTagBadge';
1415

1516
import styles from './InspectedElementBadges.css';
1617

1718
type Props = {
1819
hocDisplayNames: null | Array<string>,
1920
compiledWithForget: boolean,
21+
nativeTag: number | null,
2022
};
2123

2224
export default function InspectedElementBadges({
2325
hocDisplayNames,
2426
compiledWithForget,
27+
nativeTag,
2528
}: Props): React.Node {
2629
if (
2730
!compiledWithForget &&
28-
(hocDisplayNames == null || hocDisplayNames.length === 0)
31+
(hocDisplayNames == null || hocDisplayNames.length === 0) &&
32+
nativeTag === null
2933
) {
3034
return null;
3135
}
3236

3337
return (
3438
<div className={styles.Root}>
3539
{compiledWithForget && <ForgetBadge indexable={false} />}
40+
{nativeTag !== null && <NativeTagBadge nativeTag={nativeTag} />}
3641

3742
{hocDisplayNames !== null &&
3843
hocDisplayNames.map(hocDisplayName => (

packages/react-devtools-shared/src/devtools/views/Components/InspectedElementView.js

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,8 +54,14 @@ export default function InspectedElementView({
5454
toggleParseHookNames,
5555
symbolicatedSourcePromise,
5656
}: 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;
5965

6066
const bridge = useContext(BridgeContext);
6167
const store = useContext(StoreContext);
@@ -75,6 +81,7 @@ export default function InspectedElementView({
7581
<InspectedElementBadges
7682
hocDisplayNames={element.hocDisplayNames}
7783
compiledWithForget={element.compiledWithForget}
84+
nativeTag={nativeTag}
7885
/>
7986
</div>
8087

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -259,6 +259,9 @@ export type InspectedElement = {
259259

260260
// UI plugins/visualizations for the inspected element.
261261
plugins: Plugins,
262+
263+
// React Native only.
264+
nativeTag: number | null,
262265
};
263266

264267
// TODO: Add profiling type

0 commit comments

Comments
 (0)