Skip to content

Commit 467275b

Browse files
committed
feat[devtools]: display Forget badge for the required components
1 parent aec521a commit 467275b

29 files changed

+428
-225
lines changed

packages/react-devtools-shared/src/__tests__/inspectedElement-test.js

+2
Original file line numberDiff line numberDiff line change
@@ -2765,13 +2765,15 @@ describe('InspectedElement', () => {
27652765
expect(inspectedElement.owners).toMatchInlineSnapshot(`
27662766
[
27672767
{
2768+
"compiledWithForget": false,
27682769
"displayName": "Child",
27692770
"hocDisplayNames": null,
27702771
"id": 3,
27712772
"key": null,
27722773
"type": 5,
27732774
},
27742775
{
2776+
"compiledWithForget": false,
27752777
"displayName": "App",
27762778
"hocDisplayNames": null,
27772779
"id": 2,

packages/react-devtools-shared/src/__tests__/profilingCache-test.js

+2
Original file line numberDiff line numberDiff line change
@@ -968,6 +968,7 @@ describe('ProfilingCache', () => {
968968
"timestamp": 0,
969969
"updaters": [
970970
{
971+
"compiledWithForget": false,
971972
"displayName": "render()",
972973
"hocDisplayNames": null,
973974
"id": 1,
@@ -1010,6 +1011,7 @@ describe('ProfilingCache', () => {
10101011
"timestamp": 0,
10111012
"updaters": [
10121013
{
1014+
"compiledWithForget": false,
10131015
"displayName": "render()",
10141016
"hocDisplayNames": null,
10151017
"id": 1,

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

+16-1
Original file line numberDiff line numberDiff line change
@@ -424,7 +424,10 @@ export function getInternalReactConstants(version: string): {
424424
}
425425

426426
// NOTICE Keep in sync with shouldFilterFiber() and other get*ForFiber methods
427-
function getDisplayNameForFiber(fiber: Fiber): string | null {
427+
function getDisplayNameForFiber(
428+
fiber: Fiber,
429+
shouldSkipForgetCheck: boolean = false,
430+
): string | null {
428431
const {elementType, type, tag} = fiber;
429432

430433
let resolvedType = type;
@@ -433,6 +436,18 @@ export function getInternalReactConstants(version: string): {
433436
}
434437

435438
let resolvedContext: any = null;
439+
// $FlowFixMe[incompatible-type] fiber.updateQueue is mixed
440+
if (!shouldSkipForgetCheck && fiber.updateQueue?.memoCache != null) {
441+
const displayNameWithoutForgetWrapper = getDisplayNameForFiber(
442+
fiber,
443+
true,
444+
);
445+
if (displayNameWithoutForgetWrapper == null) {
446+
return null;
447+
}
448+
449+
return `Forget(${displayNameWithoutForgetWrapper})`;
450+
}
436451

437452
switch (tag) {
438453
case CacheComponent:

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

+2-12
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
*/
99

1010
import {hydrate, fillInPath} from 'react-devtools-shared/src/hydration';
11-
import {separateDisplayNameAndHOCs} from 'react-devtools-shared/src/utils';
11+
import {backendToFrontendSerializedElementMapper} from 'react-devtools-shared/src/utils';
1212
import Store from 'react-devtools-shared/src/devtools/store';
1313
import TimeoutError from 'react-devtools-shared/src/errors/TimeoutError';
1414
import ElementPollingCancellationError from 'react-devtools-shared/src/errors/ElementPollingCancellationError';
@@ -266,17 +266,7 @@ export function convertInspectedElementBackendToFrontend(
266266
owners:
267267
owners === null
268268
? null
269-
: owners.map(owner => {
270-
const [displayName, hocDisplayNames] = separateDisplayNameAndHOCs(
271-
owner.displayName,
272-
owner.type,
273-
);
274-
return {
275-
...owner,
276-
displayName,
277-
hocDisplayNames,
278-
};
279-
}),
269+
: owners.map(backendToFrontendSerializedElementMapper),
280270
context: hydrateHelper(context),
281271
hooks: hydrateHelper(hooks),
282272
props: hydrateHelper(props),

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

+2
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ export const THEME_STYLES: {[style: Theme | DisplayDensity]: any, ...} = {
7777
'--color-error-border': 'hsl(0, 100%, 92%)',
7878
'--color-error-text': '#ff0000',
7979
'--color-expand-collapse-toggle': '#777d88',
80+
'--color-forget-badge': '#2683E2',
8081
'--color-link': '#0000ff',
8182
'--color-modal-background': 'rgba(255, 255, 255, 0.75)',
8283
'--color-bridge-version-npm-background': '#eff0f1',
@@ -221,6 +222,7 @@ export const THEME_STYLES: {[style: Theme | DisplayDensity]: any, ...} = {
221222
'--color-error-border': '#900',
222223
'--color-error-text': '#f55',
223224
'--color-expand-collapse-toggle': '#8f949d',
225+
'--color-forget-badge': '#2683E2',
224226
'--color-link': '#61dafb',
225227
'--color-modal-background': 'rgba(0, 0, 0, 0.75)',
226228
'--color-bridge-version-npm-background': 'rgba(0, 0, 0, 0.25)',

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

+8-3
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,9 @@ import {ElementTypeRoot} from '../frontend/types';
2525
import {
2626
getSavedComponentFilters,
2727
setSavedComponentFilters,
28-
separateDisplayNameAndHOCs,
2928
shallowDiffers,
3029
utfDecodeStringWithRanges,
30+
parseElementDisplayNameFromBackend,
3131
} from '../utils';
3232
import {localStorageGetItem, localStorageSetItem} from '../storage';
3333
import {__DEBUG__} from '../constants';
@@ -1033,6 +1033,7 @@ export default class Store extends EventEmitter<{
10331033
parentID: 0,
10341034
type,
10351035
weight: 0,
1036+
compiledWithForget: false,
10361037
});
10371038

10381039
haveRootsChanged = true;
@@ -1071,8 +1072,11 @@ export default class Store extends EventEmitter<{
10711072

10721073
parentElement.children.push(id);
10731074

1074-
const [displayNameWithoutHOCs, hocDisplayNames] =
1075-
separateDisplayNameAndHOCs(displayName, type);
1075+
const {
1076+
formattedDisplayName: displayNameWithoutHOCs,
1077+
hocDisplayNames,
1078+
compiledWithForget,
1079+
} = parseElementDisplayNameFromBackend(displayName, type);
10761080

10771081
const element: Element = {
10781082
children: [],
@@ -1087,6 +1091,7 @@ export default class Store extends EventEmitter<{
10871091
parentID,
10881092
type,
10891093
weight: 1,
1094+
compiledWithForget,
10901095
};
10911096

10921097
this._idToElement.set(id, element);

packages/react-devtools-shared/src/devtools/views/Components/Badge.css

-6
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,3 @@
99
font-family: var(--font-family-monospace);
1010
font-size: var(--font-size-monospace-small);
1111
}
12-
13-
.ExtraLabel {
14-
font-family: var(--font-family-monospace);
15-
font-size: var(--font-size-monospace-small);
16-
color: var(--color-component-badge-count);
17-
}

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

+3-25
Original file line numberDiff line numberDiff line change
@@ -8,36 +8,14 @@
88
*/
99

1010
import * as React from 'react';
11-
import {Fragment} from 'react';
12-
import styles from './Badge.css';
1311

14-
import type {ElementType} from 'react-devtools-shared/src/frontend/types';
12+
import styles from './Badge.css';
1513

1614
type Props = {
1715
className?: string,
18-
hocDisplayNames: Array<string> | null,
19-
type: ElementType,
2016
children: React$Node,
2117
};
2218

23-
export default function Badge({
24-
className,
25-
hocDisplayNames,
26-
type,
27-
children,
28-
}: Props): React.Node {
29-
if (hocDisplayNames === null || hocDisplayNames.length === 0) {
30-
return null;
31-
}
32-
33-
const totalBadgeCount = hocDisplayNames.length;
34-
35-
return (
36-
<Fragment>
37-
<div className={`${styles.Badge} ${className || ''}`}>{children}</div>
38-
{totalBadgeCount > 1 && (
39-
<div className={styles.ExtraLabel}>+{totalBadgeCount - 1}</div>
40-
)}
41-
</Fragment>
42-
);
19+
export default function Badge({className = '', children}: Props): React.Node {
20+
return <div className={`${styles.Badge} ${className}`}>{children}</div>;
4321
}

packages/react-devtools-shared/src/devtools/views/Components/Element.css

+1-1
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@
6565
color: var(--color-expand-collapse-toggle);
6666
}
6767

68-
.Badge {
68+
.BadgesBlock {
6969
margin-left: 0.25rem;
7070
}
7171

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

+12-58
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,14 @@
1010
import * as React from 'react';
1111
import {Fragment, useContext, useMemo, useState} from 'react';
1212
import Store from 'react-devtools-shared/src/devtools/store';
13-
import Badge from './Badge';
1413
import ButtonIcon from '../ButtonIcon';
15-
import {createRegExp} from '../utils';
1614
import {TreeDispatcherContext, TreeStateContext} from './TreeContext';
1715
import {SettingsContext} from '../Settings/SettingsContext';
1816
import {StoreContext} from '../context';
1917
import {useSubscription} from '../hooks';
2018
import {logEvent} from 'react-devtools-shared/src/Logger';
19+
import IndexableElementBadges from './IndexableElementBadges';
20+
import IndexableDisplayName from './IndexableDisplayName';
2121

2222
import type {ItemData} from './Tree';
2323
import type {Element as ElementType} from 'react-devtools-shared/src/frontend/types';
@@ -121,7 +121,7 @@ export default function Element({data, index, style}: Props): React.Node {
121121
hocDisplayNames,
122122
isStrictModeNonCompliant,
123123
key,
124-
type,
124+
compiledWithForget,
125125
} = element;
126126

127127
// Only show strict mode non-compliance badges for top level elements.
@@ -155,11 +155,11 @@ export default function Element({data, index, style}: Props): React.Node {
155155
// We must use padding rather than margin/left because of the selected background color.
156156
transform: `translateX(calc(${depth} * var(--indentation-size)))`,
157157
}}>
158-
{ownerID === null ? (
158+
{ownerID === null && (
159159
<ExpandCollapseToggle element={element} store={store} />
160-
) : null}
160+
)}
161161

162-
<DisplayName displayName={displayName} id={((id: any): number)} />
162+
<IndexableDisplayName displayName={displayName} id={id} />
163163

164164
{key && (
165165
<Fragment>
@@ -174,14 +174,12 @@ export default function Element({data, index, style}: Props): React.Node {
174174
</Fragment>
175175
)}
176176

177-
{hocDisplayNames !== null && hocDisplayNames.length > 0 ? (
178-
<Badge
179-
className={styles.Badge}
180-
hocDisplayNames={hocDisplayNames}
181-
type={type}>
182-
<DisplayName displayName={hocDisplayNames[0]} id={id} />
183-
</Badge>
184-
) : null}
177+
<IndexableElementBadges
178+
hocDisplayNames={hocDisplayNames}
179+
compiledWithForget={compiledWithForget}
180+
elementID={id}
181+
className={styles.BadgesBlock}
182+
/>
185183

186184
{showInlineWarningsAndErrors && errorCount > 0 && (
187185
<Icon
@@ -262,47 +260,3 @@ function ExpandCollapseToggle({element, store}: ExpandCollapseToggleProps) {
262260
</div>
263261
);
264262
}
265-
266-
type DisplayNameProps = {
267-
displayName: string | null,
268-
id: number,
269-
};
270-
271-
function DisplayName({displayName, id}: DisplayNameProps) {
272-
const {searchIndex, searchResults, searchText} = useContext(TreeStateContext);
273-
const isSearchResult = useMemo(() => {
274-
return searchResults.includes(id);
275-
}, [id, searchResults]);
276-
const isCurrentResult =
277-
searchIndex !== null && id === searchResults[searchIndex];
278-
279-
if (!isSearchResult || displayName === null) {
280-
return displayName;
281-
}
282-
283-
const match = createRegExp(searchText).exec(displayName);
284-
285-
if (match === null) {
286-
return displayName;
287-
}
288-
289-
const startIndex = match.index;
290-
const stopIndex = startIndex + match[0].length;
291-
292-
const children = [];
293-
if (startIndex > 0) {
294-
children.push(<span key="begin">{displayName.slice(0, startIndex)}</span>);
295-
}
296-
children.push(
297-
<mark
298-
key="middle"
299-
className={isCurrentResult ? styles.CurrentHighlight : styles.Highlight}>
300-
{displayName.slice(startIndex, stopIndex)}
301-
</mark>,
302-
);
303-
if (stopIndex < displayName.length) {
304-
children.push(<span key="end">{displayName.slice(stopIndex)}</span>);
305-
}
306-
307-
return children;
308-
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
.Root {
2+
display: inline-flex;
3+
align-items: center;
4+
}
5+
6+
.Root *:not(:first-child) {
7+
margin-left: 0.25rem;
8+
}
9+
10+
.ExtraLabel {
11+
font-family: var(--font-family-monospace);
12+
font-size: var(--font-size-monospace-small);
13+
color: var(--color-component-badge-count);
14+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
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 ForgetBadge from './ForgetBadge';
14+
15+
import styles from './ElementBadges.css';
16+
17+
type Props = {
18+
hocDisplayNames: Array<string> | null,
19+
compiledWithForget: boolean,
20+
className?: string,
21+
};
22+
23+
export default function ElementBadges({
24+
compiledWithForget,
25+
hocDisplayNames,
26+
className = '',
27+
}: Props): React.Node {
28+
if (
29+
!compiledWithForget &&
30+
(hocDisplayNames == null || hocDisplayNames.length === 0)
31+
) {
32+
return null;
33+
}
34+
35+
return (
36+
<div className={`${styles.Root} ${className}`}>
37+
{compiledWithForget && <ForgetBadge indexable={false} />}
38+
39+
{hocDisplayNames != null && hocDisplayNames.length > 0 && (
40+
<Badge>{hocDisplayNames[0]}</Badge>
41+
)}
42+
43+
{hocDisplayNames != null && hocDisplayNames.length > 1 && (
44+
<div className={styles.ExtraLabel}>+{hocDisplayNames.length - 1}</div>
45+
)}
46+
</div>
47+
);
48+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
.Root {
2+
background-color: var(--color-forget-badge);
3+
}

0 commit comments

Comments
 (0)