Skip to content

Commit 1974d08

Browse files
authored
[DevTools] Fix Bugs With Component Stacks (#24815)
This PR: * Simplifies the code in `SidebarEventInfo` by passing it the actual clicked event rather than an index. * Lightly refactored the `SidebarEventInfo` code so that it can be used for more than just `schedulingEvents` * Fixes bug. Previously, whenever a state update event was clicked, we updated the `selectedCommitIndex` in the `ProfilerContext`. However, this index is used for the selected commit in the Flamegraph profiler, which caused a bug where if you would change the contents of the event sidebar, the commit sidebar in the Flamegraph profiler would change too. This PR replaces this with the actual event info instead
1 parent cd80d32 commit 1974d08

File tree

5 files changed

+78
-70
lines changed

5 files changed

+78
-70
lines changed

packages/react-devtools-shared/src/devtools/views/Profiler/SidebarEventInfo.js

Lines changed: 49 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -7,80 +7,73 @@
77
* @flow
88
*/
99

10+
import type {SchedulingEvent} from 'react-devtools-timeline/src/types';
11+
1012
import * as React from 'react';
11-
import {isStateUpdateEvent} from 'react-devtools-timeline/src/utils/flow';
1213
import Button from '../Button';
1314
import ButtonIcon from '../ButtonIcon';
1415
import ViewSourceContext from '../Components/ViewSourceContext';
15-
import {useContext, useMemo} from 'react';
16-
import {ProfilerContext} from './ProfilerContext';
16+
import {useContext} from 'react';
17+
import {TimelineContext} from 'react-devtools-timeline/src/TimelineContext';
1718
import {stackToComponentSources} from 'react-devtools-shared/src/devtools/utils';
1819

1920
import styles from './SidebarEventInfo.css';
2021

2122
export type Props = {||};
2223

23-
export default function SidebarEventInfo(_: Props) {
24-
const {profilingData, selectedCommitIndex} = useContext(ProfilerContext);
24+
function SchedulingEventInfo({eventInfo}: {eventInfo: SchedulingEvent}) {
2525
const {viewUrlSourceFunction} = useContext(ViewSourceContext);
2626

27-
const {stack} = useMemo(() => {
28-
if (
29-
selectedCommitIndex == null ||
30-
profilingData == null ||
31-
profilingData.timelineData.length === 0
32-
) {
33-
return {};
34-
}
35-
const {schedulingEvents} = profilingData.timelineData[0];
27+
const componentStack = eventInfo.componentStack
28+
? stackToComponentSources(eventInfo.componentStack)
29+
: null;
3630

37-
const event = schedulingEvents[selectedCommitIndex];
38-
if (!isStateUpdateEvent(event)) {
39-
return {};
31+
const viewSource = source => {
32+
if (viewUrlSourceFunction != null && source != null) {
33+
viewUrlSourceFunction(...source);
4034
}
35+
};
4136

42-
let componentStack = null;
43-
if (event.componentStack) {
44-
componentStack = stackToComponentSources(event.componentStack);
45-
}
46-
47-
return {
48-
stack: componentStack,
49-
};
50-
}, [profilingData, selectedCommitIndex]);
51-
52-
let components;
53-
if (stack) {
54-
components = stack.map(([displayName, source], index) => {
55-
const hasSource = source != null;
56-
57-
const onClick = () => {
58-
if (viewUrlSourceFunction != null && source != null) {
59-
viewUrlSourceFunction(...source);
60-
}
61-
};
37+
return (
38+
<div className={styles.Content} tabIndex={0}>
39+
{componentStack ? (
40+
<ol className={styles.List}>
41+
{componentStack.map(([displayName, source], index) => {
42+
const hasSource = source != null;
6243

63-
return (
64-
<li key={index} className={styles.ListItem} data-source={hasSource}>
65-
<label className={styles.Label}>
66-
<Button className={styles.Button} onClick={onClick}>
67-
{displayName}
68-
</Button>
69-
{hasSource && (
70-
<ButtonIcon className={styles.Source} type="view-source" />
71-
)}
72-
</label>
73-
</li>
74-
);
75-
});
76-
}
44+
return (
45+
<li
46+
key={index}
47+
className={styles.ListItem}
48+
data-source={hasSource}>
49+
<label className={styles.Label}>
50+
<Button
51+
className={styles.Button}
52+
onClick={() => viewSource(source)}>
53+
{displayName}
54+
</Button>
55+
{hasSource && (
56+
<ButtonIcon className={styles.Source} type="view-source" />
57+
)}
58+
</label>
59+
</li>
60+
);
61+
})}
62+
</ol>
63+
) : null}
64+
</div>
65+
);
66+
}
7767

78-
return (
68+
export default function SidebarEventInfo(_: Props) {
69+
const {selectedEvent} = useContext(TimelineContext);
70+
// (TODO) Refactor in next PR so this supports multiple types of events
71+
return selectedEvent ? (
7972
<>
8073
<div className={styles.Toolbar}>Event Component Tree</div>
81-
<div className={styles.Content} tabIndex={0}>
82-
<ol className={styles.List}>{components}</ol>
83-
</div>
74+
{selectedEvent.schedulingEvent ? (
75+
<SchedulingEventInfo eventInfo={selectedEvent.schedulingEvent} />
76+
) : null}
8477
</>
85-
);
78+
) : null;
8679
}

packages/react-devtools-timeline/src/CanvasPage.js

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99

1010
import type {Point} from './view-base';
1111
import type {
12-
ReactHoverContextInfo,
12+
ReactEventInfo,
1313
TimelineData,
1414
ReactMeasure,
1515
ViewState,
@@ -63,7 +63,7 @@ import useContextMenu from 'react-devtools-shared/src/devtools/ContextMenu/useCo
6363
import {getBatchRange} from './utils/getBatchRange';
6464
import {MAX_ZOOM_LEVEL, MIN_ZOOM_LEVEL} from './view-base/constants';
6565
import {TimelineSearchContext} from './TimelineSearchContext';
66-
import {ProfilerContext} from 'react-devtools-shared/src/devtools/views/Profiler/ProfilerContext';
66+
import {TimelineContext} from './TimelineContext';
6767

6868
import styles from './CanvasPage.css';
6969

@@ -132,7 +132,7 @@ const zoomToBatch = (
132132
viewState.updateHorizontalScrollState(scrollState);
133133
};
134134

135-
const EMPTY_CONTEXT_INFO: ReactHoverContextInfo = {
135+
const EMPTY_CONTEXT_INFO: ReactEventInfo = {
136136
componentMeasure: null,
137137
flamechartStackFrame: null,
138138
measure: null,
@@ -162,10 +162,7 @@ function AutoSizedCanvas({
162162

163163
const [isContextMenuShown, setIsContextMenuShown] = useState<boolean>(false);
164164
const [mouseLocation, setMouseLocation] = useState<Point>(zeroPoint); // DOM coordinates
165-
const [
166-
hoveredEvent,
167-
setHoveredEvent,
168-
] = useState<ReactHoverContextInfo | null>(null);
165+
const [hoveredEvent, setHoveredEvent] = useState<ReactEventInfo | null>(null);
169166

170167
const resetHoveredEvent = useCallback(
171168
() => setHoveredEvent(EMPTY_CONTEXT_INFO),
@@ -529,7 +526,7 @@ function AutoSizedCanvas({
529526
ref: canvasRef,
530527
});
531528

532-
const {selectCommitIndex} = useContext(ProfilerContext);
529+
const {selectEvent} = useContext(TimelineContext);
533530

534531
useEffect(() => {
535532
const {current: userTimingMarksView} = userTimingMarksViewRef;
@@ -566,8 +563,11 @@ function AutoSizedCanvas({
566563
});
567564
}
568565
};
569-
schedulingEventsView.onClick = (schedulingEvent, eventIndex) => {
570-
selectCommitIndex(eventIndex);
566+
schedulingEventsView.onClick = schedulingEvent => {
567+
selectEvent({
568+
...EMPTY_CONTEXT_INFO,
569+
schedulingEvent,
570+
});
571571
};
572572
}
573573

packages/react-devtools-timeline/src/EventTooltip.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import type {
1313
NativeEvent,
1414
NetworkMeasure,
1515
ReactComponentMeasure,
16-
ReactHoverContextInfo,
16+
ReactEventInfo,
1717
ReactMeasure,
1818
TimelineData,
1919
SchedulingEvent,
@@ -35,7 +35,7 @@ type Props = {|
3535
canvasRef: {|current: HTMLCanvasElement | null|},
3636
data: TimelineData,
3737
height: number,
38-
hoveredEvent: ReactHoverContextInfo | null,
38+
hoveredEvent: ReactEventInfo | null,
3939
origin: Point,
4040
width: number,
4141
|};

packages/react-devtools-timeline/src/TimelineContext.js

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import type {
2323
TimelineData,
2424
SearchRegExpStateChangeCallback,
2525
ViewState,
26+
ReactEventInfo,
2627
} from './types';
2728
import type {RefObject} from 'shared/ReactTypes';
2829

@@ -33,6 +34,8 @@ export type Context = {|
3334
searchInputContainerRef: RefObject,
3435
setFile: (file: File | null) => void,
3536
viewState: ViewState,
37+
selectEvent: ReactEventInfo => void,
38+
selectedEvent: ReactEventInfo,
3639
|};
3740

3841
const TimelineContext = createContext<Context>(((null: any): Context));
@@ -121,6 +124,8 @@ function TimelineContextController({children}: Props) {
121124
return state;
122125
}, [file]);
123126

127+
const [selectedEvent, selectEvent] = useState<ReactEventInfo | null>(null);
128+
124129
const value = useMemo(
125130
() => ({
126131
file,
@@ -129,8 +134,18 @@ function TimelineContextController({children}: Props) {
129134
searchInputContainerRef,
130135
setFile,
131136
viewState,
137+
selectEvent,
138+
selectedEvent,
132139
}),
133-
[file, inMemoryTimelineData, isTimelineSupported, setFile, viewState],
140+
[
141+
file,
142+
inMemoryTimelineData,
143+
isTimelineSupported,
144+
setFile,
145+
viewState,
146+
selectEvent,
147+
selectedEvent,
148+
],
134149
);
135150

136151
return (

packages/react-devtools-timeline/src/types.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -240,7 +240,7 @@ export type TimelineDataExport = {|
240240
thrownErrors: ThrownError[],
241241
|};
242242

243-
export type ReactHoverContextInfo = {|
243+
export type ReactEventInfo = {|
244244
componentMeasure: ReactComponentMeasure | null,
245245
flamechartStackFrame: FlamechartStackFrame | null,
246246
measure: ReactMeasure | null,

0 commit comments

Comments
 (0)