Skip to content

Commit 7972aba

Browse files
committed
[DevTools] Avoid renders of stale Suspense store
1 parent b9a0453 commit 7972aba

File tree

4 files changed

+33
-18
lines changed

4 files changed

+33
-18
lines changed

packages/react-devtools-shared/src/devtools/views/SuspenseTab/SuspenseBreadcrumbs.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,20 +8,20 @@
88
*/
99

1010
import type {SuspenseNode} from 'react-devtools-shared/src/frontend/types';
11+
import typeof {SyntheticMouseEvent} from 'react-dom-bindings/src/events/SyntheticEvent';
1112

1213
import * as React from 'react';
1314
import {useContext} from 'react';
1415
import {
1516
TreeDispatcherContext,
1617
TreeStateContext,
1718
} from '../Components/TreeContext';
18-
import {StoreContext} from '../context';
1919
import {useHighlightHostInstance} from '../hooks';
2020
import styles from './SuspenseBreadcrumbs.css';
21-
import typeof {SyntheticMouseEvent} from 'react-dom-bindings/src/events/SyntheticEvent';
21+
import {useSuspenseStore} from './SuspenseTreeContext';
2222

2323
export default function SuspenseBreadcrumbs(): React$Node {
24-
const store = useContext(StoreContext);
24+
const store = useSuspenseStore();
2525
const dispatch = useContext(TreeDispatcherContext);
2626
const {inspectedElementID} = useContext(TreeStateContext);
2727

packages/react-devtools-shared/src/devtools/views/SuspenseTab/SuspenseRects.js

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,12 @@ import {
1919
TreeDispatcherContext,
2020
TreeStateContext,
2121
} from '../Components/TreeContext';
22-
import {StoreContext} from '../context';
2322
import {useHighlightHostInstance} from '../hooks';
2423
import styles from './SuspenseRects.css';
25-
import {SuspenseTreeStateContext} from './SuspenseTreeContext';
24+
import {
25+
SuspenseTreeStateContext,
26+
useSuspenseStore,
27+
} from './SuspenseTreeContext';
2628
import typeof {
2729
SyntheticMouseEvent,
2830
SyntheticPointerEvent,
@@ -46,7 +48,7 @@ function SuspenseRects({
4648
suspenseID: SuspenseNode['id'],
4749
}): React$Node {
4850
const dispatch = useContext(TreeDispatcherContext);
49-
const store = useContext(StoreContext);
51+
const store = useSuspenseStore();
5052

5153
const {inspectedElementID} = useContext(TreeStateContext);
5254

@@ -158,7 +160,7 @@ function SuspenseRectsShell({
158160
}: {
159161
shellID: SuspenseNode['id'],
160162
}): React$Node {
161-
const store = useContext(StoreContext);
163+
const store = useSuspenseStore();
162164
const shell = store.getSuspenseByID(shellID);
163165
if (shell === null) {
164166
console.warn(`<Element> Could not find suspense node id ${shellID}`);
@@ -175,7 +177,7 @@ function SuspenseRectsShell({
175177
}
176178

177179
function SuspenseRectsContainer(): React$Node {
178-
const store = useContext(StoreContext);
180+
const store = useSuspenseStore();
179181
// TODO: This relies on a full re-render of all children when the Suspense tree changes.
180182
const {shells} = useContext(SuspenseTreeStateContext);
181183

packages/react-devtools-shared/src/devtools/views/SuspenseTab/SuspenseTimeline.js

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,13 @@ import type Store from '../../store';
1212

1313
import * as React from 'react';
1414
import {useContext, useLayoutEffect, useMemo, useRef, useState} from 'react';
15-
import {BridgeContext, StoreContext} from '../context';
15+
import {BridgeContext} from '../context';
1616
import {TreeDispatcherContext} from '../Components/TreeContext';
1717
import {useHighlightHostInstance} from '../hooks';
18-
import {SuspenseTreeStateContext} from './SuspenseTreeContext';
18+
import {
19+
SuspenseTreeStateContext,
20+
useSuspenseStore,
21+
} from './SuspenseTreeContext';
1922
import styles from './SuspenseTimeline.css';
2023
import typeof {
2124
SyntheticEvent,
@@ -63,7 +66,7 @@ function getSuspendableDocumentOrderSuspense(
6366

6467
function SuspenseTimelineInput({rootID}: {rootID: Element['id'] | void}) {
6568
const bridge = useContext(BridgeContext);
66-
const store = useContext(StoreContext);
69+
const store = useSuspenseStore();
6770
const dispatch = useContext(TreeDispatcherContext);
6871
const {highlightHostInstance, clearHighlightHostInstance} =
6972
useHighlightHostInstance();
@@ -161,6 +164,7 @@ function SuspenseTimelineInput({rootID}: {rootID: Element['id'] | void}) {
161164
function handleFocus() {
162165
const suspense = timeline[value];
163166

167+
dispatch({type: 'SELECT_ELEMENT_BY_ID', payload: suspense.id});
164168
highlightHostInstance(suspense.id);
165169
}
166170

@@ -213,7 +217,7 @@ function SuspenseTimelineInput({rootID}: {rootID: Element['id'] | void}) {
213217
}
214218

215219
export default function SuspenseTimeline(): React$Node {
216-
const store = useContext(StoreContext);
220+
const store = useSuspenseStore();
217221
const {shells} = useContext(SuspenseTreeStateContext);
218222

219223
const defaultSelectedRootID = shells.find(rootID => {
@@ -243,16 +247,14 @@ export default function SuspenseTimeline(): React$Node {
243247
<select
244248
aria-label="Select Suspense Root"
245249
className={styles.SuspenseTimelineRootSwitcher}
246-
onChange={handleChange}>
250+
onChange={handleChange}
251+
value={selectedRootID}>
247252
{shells.map(rootID => {
248253
// TODO: Use name
249254
const name = '#' + rootID;
250255
// TODO: Highlight host on hover
251256
return (
252-
<option
253-
key={rootID}
254-
selected={rootID === selectedRootID}
255-
value={rootID}>
257+
<option key={rootID} value={rootID}>
256258
{name}
257259
</option>
258260
);

packages/react-devtools-shared/src/devtools/views/SuspenseTab/SuspenseTreeContext.js

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
* @flow
88
*/
99
import type {ReactContext} from 'shared/ReactTypes';
10+
import type {SuspenseNode} from '../../../frontend/types';
11+
import type Store from '../../store';
1012

1113
import * as React from 'react';
1214
import {
@@ -17,7 +19,6 @@ import {
1719
useMemo,
1820
useReducer,
1921
} from 'react';
20-
import type {SuspenseNode} from '../../../frontend/types';
2122
import {StoreContext} from '../context';
2223

2324
export type SuspenseTreeState = {
@@ -110,8 +111,18 @@ function SuspenseTreeContextController({children}: Props): React.Node {
110111
);
111112
}
112113

114+
function useSuspenseStore(): Store {
115+
const store = useContext(StoreContext);
116+
// We're currently storing everything Suspense related in the same Store as
117+
// Components. However, most reads are currently stateless. This ensures
118+
// the latest state is always read from the Store.
119+
useContext(SuspenseTreeStateContext);
120+
return store;
121+
}
122+
113123
export {
114124
SuspenseTreeDispatcherContext,
115125
SuspenseTreeStateContext,
116126
SuspenseTreeContextController,
127+
useSuspenseStore,
117128
};

0 commit comments

Comments
 (0)