[DevTools] Don't call Hooks conditionally (#34644)
Sebastian "Sebbie" Silbermann committed
Sep 29, 2025 at 15:15 UTC
2bbb7be0e193e53ee625dfd496a30bcec60bebd5
2 files changed
+50
-40
packages/react-devtools-shared/src/devtools/views/Profiler/SidebarEventInfo.js
+28
-18
@@ -8,6 +8,7 @@
8
*/
9
10
import type {SchedulingEvent} from 'react-devtools-timeline/src/types';
11
+import type {ReactFunctionLocation} from 'shared/ReactTypes';
12
13
import * as React from 'react';
14
import Button from '../Button';
@@ -27,6 +28,28 @@ import styles from './SidebarEventInfo.css';
28
29
export type Props = {};
30
31
+type FunctionLocationProps = {
32
+ location: ReactFunctionLocation,
33
+ displayName: string,
34
+};
35
+function FunctionLocation({location, displayName}: FunctionLocationProps) {
36
+ // TODO: We should support symbolication here as well, but
37
+ // symbolicating the whole stack can be expensive
38
+ const [canViewSource, viewSource] = useOpenResource(location, null);
39
+ return (
40
+ <li>
41
+ <Button
42
+ className={
43
+ canViewSource ? styles.ClickableSource : styles.UnclickableSource
44
+ }
45
+ disabled={!canViewSource}
46
+ onClick={viewSource}>
47
+ {displayName}
48
+ </Button>
49
+ </li>
50
+ );
51
+}
52
+
53
type SchedulingEventProps = {
54
eventInfo: SchedulingEvent,
55
};
@@ -74,25 +97,12 @@ function SchedulingEventInfo({eventInfo}: SchedulingEventProps) {
97
);
98
}
99
77
- // TODO: We should support symbolication here as well, but
78
- // symbolicating the whole stack can be expensive
79
- const [canViewSource, viewSource] = useOpenResource(
80
- location,
81
- null,
82
- );
100
return (
84
- <li key={index}>
85
- <Button
86
- className={
87
- canViewSource
88
- ? styles.ClickableSource
89
- : styles.UnclickableSource
90
- }
91
- disabled={!canViewSource}
92
- onClick={viewSource}>
93
- {displayName}
94
- </Button>
95
- </li>
101
+ <FunctionLocation
102
+ key={index}
103
+ displayName={displayName}
104
+ location={location}
105
+ />
106
);
107
},
108
)}
packages/react-devtools-shared/src/devtools/views/SuspenseTab/SuspenseTimeline.js
+22
-22
@@ -39,28 +39,6 @@ function SuspenseTimelineInput() {
39
const min = 0;
40
const max = timeline.length > 0 ? timeline.length - 1 : 0;
41
42
- if (rootID === null) {
43
- return (
44
- <div className={styles.SuspenseTimelineInput}>No root selected.</div>
45
- );
46
- }
47
-
48
- if (!store.supportsTogglingSuspense(rootID)) {
49
- return (
50
- <div className={styles.SuspenseTimelineInput}>
51
- Can't step through Suspense in production apps.
52
- </div>
53
- );
54
- }
55
-
56
- if (timeline.length === 0) {
57
- return (
58
- <div className={styles.SuspenseTimelineInput}>
59
- Root contains no Suspense nodes.
60
- </div>
61
- );
62
- }
63
-
42
function switchSuspenseNode(nextTimelineIndex: number) {
43
const nextSelectedSuspenseID = timeline[nextTimelineIndex];
44
highlightHostInstance(nextSelectedSuspenseID);
@@ -175,6 +153,28 @@ function SuspenseTimelineInput() {
153
};
154
}, [playing]);
155
156
+ if (rootID === null) {
157
+ return (
158
+ <div className={styles.SuspenseTimelineInput}>No root selected.</div>
159
+ );
160
+ }
161
+
162
+ if (!store.supportsTogglingSuspense(rootID)) {
163
+ return (
164
+ <div className={styles.SuspenseTimelineInput}>
165
+ Can't step through Suspense in production apps.
166
+ </div>
167
+ );
168
+ }
169
+
170
+ if (timeline.length === 0) {
171
+ return (
172
+ <div className={styles.SuspenseTimelineInput}>
173
+ Root contains no Suspense nodes.
174
+ </div>
175
+ );
176
+ }
177
+
178
return (
179
<>
180
<Button