Fix DevTools handling of empty Suspense tag for legacy renderer versions (#19337)
Brian Vaughn committed
Jul 13, 2020 at 16:21 UTC
fbc63863692d291b50e55400673845f7c81aff61
4 files changed
+90
-3
packages/react-devtools-shared/src/__tests__/__snapshots__/profilingCache-test.js.snap
+59
@@ -2191,6 +2191,65 @@ Object {
2191
}
2192
`;
2193
2194
+exports[`ProfilingCache should handle unexpectedly shallow suspense trees: Empty Suspense node 1`] = `
2195
+Object {
2196
+ "commitData": Array [
2197
+ Object {
2198
+ "changeDescriptions": Map {},
2199
+ "duration": 0,
2200
+ "fiberActualDurations": Map {
2201
+ 1 => 0,
2202
+ 2 => 0,
2203
+ },
2204
+ "fiberSelfDurations": Map {
2205
+ 1 => 0,
2206
+ 2 => 0,
2207
+ },
2208
+ "interactionIDs": Array [],
2209
+ "priorityLevel": "Normal",
2210
+ "timestamp": 0,
2211
+ },
2212
+ ],
2213
+ "displayName": "Suspense",
2214
+ "initialTreeBaseDurations": Map {},
2215
+ "interactionCommits": Map {},
2216
+ "interactions": Map {},
2217
+ "operations": Array [
2218
+ Array [
2219
+ 1,
2220
+ 1,
2221
+ 9,
2222
+ 8,
2223
+ 83,
2224
+ 117,
2225
+ 115,
2226
+ 112,
2227
+ 101,
2228
+ 110,
2229
+ 115,
2230
+ 101,
2231
+ 1,
2232
+ 1,
2233
+ 11,
2234
+ 1,
2235
+ 1,
2236
+ 1,
2237
+ 2,
2238
+ 12,
2239
+ 1,
2240
+ 0,
2241
+ 1,
2242
+ 0,
2243
+ 4,
2244
+ 2,
2245
+ 0,
2246
+ ],
2247
+ ],
2248
+ "rootID": 1,
2249
+ "snapshots": Map {},
2250
+}
2251
+`;
2252
+
2253
exports[`ProfilingCache should properly detect changed hooks: CommitDetails commitIndex: 0 1`] = `
2254
Object {
2255
"changeDescriptions": Map {
packages/react-devtools-shared/src/__tests__/profilingCache-test.js
+20
@@ -696,4 +696,24 @@ describe('ProfilingCache', () => {
696
),
697
);
698
});
699
+
700
+ it('should handle unexpectedly shallow suspense trees', () => {
701
+ const container = document.createElement('div');
702
+
703
+ utils.act(() => store.profilerStore.startProfiling());
704
+ utils.act(() => ReactDOM.render(<React.Suspense />, container));
705
+ utils.act(() => store.profilerStore.stopProfiling());
706
+
707
+ function Validator({commitIndex, rootID}) {
708
+ const profilingDataForRoot = store.profilerStore.getDataForRoot(rootID);
709
+ expect(profilingDataForRoot).toMatchSnapshot('Empty Suspense node');
710
+ return null;
711
+ }
712
+
713
+ const rootID = store.roots[0];
714
+
715
+ utils.act(() => {
716
+ TestRenderer.create(<Validator commitIndex={0} rootID={rootID} />);
717
+ });
718
+ });
719
});
packages/react-devtools-shared/src/backend/renderer.js
+6
-3
@@ -1237,11 +1237,14 @@ export function attach(
1237
);
1238
}
1239
} else {
1240
+ let primaryChild: Fiber | null = null;
1241
const areSuspenseChildrenConditionallyWrapped =
1242
OffscreenComponent === -1;
1242
- const primaryChild: Fiber | null = areSuspenseChildrenConditionallyWrapped
1243
- ? fiber.child
1244
- : (fiber.child: any).child;
1243
+ if (areSuspenseChildrenConditionallyWrapped) {
1244
+ primaryChild = fiber.child;
1245
+ } else if (fiber.child !== null) {
1246
+ primaryChild = fiber.child.child;
1247
+ }
1248
if (primaryChild !== null) {
1249
mountFiberRecursively(
1250
primaryChild,
packages/react-devtools-shell/src/app/SuspenseTree/index.js
+5
@@ -25,10 +25,15 @@ function SuspenseTree() {
25
<PrimaryFallbackTest initialSuspend={true} />
26
<NestedSuspenseTest />
27
<SuspenseListTest />
28
+ <EmptySuspense />
29
</Fragment>
30
);
31
}
32
33
+function EmptySuspense() {
34
+ return <Suspense />;
35
+}
36
+
37
function PrimaryFallbackTest({initialSuspend}) {
38
const [suspend, setSuspend] = useState(initialSuspend);
39
const fallbackStep = useTestSequence('fallback', Fallback1, Fallback2);