Move SuspenseList to experimental channel (#22765)
There's more work to be done to implement this correctly on the server, so we're going to wait to release it until an 18.x minor.
Andrew Clark committed
Nov 15, 2021 at 13:12 UTC
83564712b6e2907dcffdbf5f99b4713cf6c950de
11 files changed
+89
-23
packages/react-dom/src/__tests__/ReactDOMFizzServer-test.js
+4
-1
@@ -42,7 +42,9 @@ describe('ReactDOMFizzServer', () => {
42
}
43
Stream = require('stream');
44
Suspense = React.Suspense;
45
- SuspenseList = React.SuspenseList;
45
+ if (gate(flags => flags.enableSuspenseList)) {
46
+ SuspenseList = React.SuspenseList;
47
+ }
48
49
PropTypes = require('prop-types');
50
@@ -656,6 +658,7 @@ describe('ReactDOMFizzServer', () => {
658
expect(ref.current).toBe(b);
659
});
660
661
+ // @gate enableSuspenseList
662
// @gate experimental
663
it('shows inserted items before pending in a SuspenseList as fallbacks while hydrating', async () => {
664
const ref = React.createRef();
packages/react-dom/src/__tests__/ReactDOMServerPartialHydration-test.internal.js
+7
-2
@@ -84,7 +84,9 @@ describe('ReactDOMServerPartialHydration', () => {
84
ReactDOMServer = require('react-dom/server');
85
Scheduler = require('scheduler');
86
Suspense = React.Suspense;
87
- SuspenseList = React.SuspenseList;
87
+ if (gate(flags => flags.enableSuspenseList)) {
88
+ SuspenseList = React.SuspenseList;
89
+ }
90
91
IdleEventPriority = require('react-reconciler/constants').IdleEventPriority;
92
});
@@ -1545,6 +1547,7 @@ describe('ReactDOMServerPartialHydration', () => {
1547
expect(ref.current).toBe(span);
1548
});
1549
1550
+ // @gate enableSuspenseList
1551
it('shows inserted items in a SuspenseList before content is hydrated', async () => {
1552
let suspend = false;
1553
let resolve;
@@ -1630,6 +1633,7 @@ describe('ReactDOMServerPartialHydration', () => {
1633
expect(ref.current).toBe(spanB);
1634
});
1635
1636
+ // @gate enableSuspenseList
1637
it('shows is able to hydrate boundaries even if others in a list are pending', async () => {
1638
let suspend = false;
1639
let resolve;
@@ -1704,7 +1708,7 @@ describe('ReactDOMServerPartialHydration', () => {
1708
expect(container.textContent).toBe('ALoading B');
1709
});
1710
1707
- // @gate experimental || www
1711
+ // @gate enableSuspenseList
1712
it('clears server boundaries when SuspenseList runs out of time hydrating', async () => {
1713
let suspend = false;
1714
let resolve;
@@ -1807,6 +1811,7 @@ describe('ReactDOMServerPartialHydration', () => {
1811
expect(ref.current).toBe(b);
1812
});
1813
1814
+ // @gate enableSuspenseList
1815
it('clears server boundaries when SuspenseList suspends last row hydrating', async () => {
1816
let suspend = false;
1817
let resolve;
packages/react-dom/src/__tests__/ReactDOMServerSuspense-test.internal.js
+7
-2
@@ -16,6 +16,7 @@ let ReactDOM;
16
let ReactDOMServer;
17
let ReactTestUtils;
18
let act;
19
+let SuspenseList;
20
21
function initModules() {
22
// Reset warning cache.
@@ -26,6 +27,9 @@ function initModules() {
27
ReactDOMServer = require('react-dom/server');
28
ReactTestUtils = require('react-dom/test-utils');
29
act = require('jest-react').act;
30
+ if (gate(flags => flags.enableSuspenseList)) {
31
+ SuspenseList = React.SuspenseList;
32
+ }
33
34
// Make them available to the helpers.
35
return {
@@ -137,16 +141,17 @@ describe('ReactDOMServerSuspense', () => {
141
);
142
});
143
144
+ // @gate enableSuspenseList
145
it('server renders a SuspenseList component and its children', async () => {
146
const example = (
142
- <React.SuspenseList>
147
+ <SuspenseList>
148
<React.Suspense fallback="Loading A">
149
<div>A</div>
150
</React.Suspense>
151
<React.Suspense fallback="Loading B">
152
<div>B</div>
153
</React.Suspense>
149
- </React.SuspenseList>
154
+ </SuspenseList>
155
);
156
const element = await serverRender(example);
157
const parent = element.parentNode;
packages/react-dom/src/__tests__/ReactWrongReturnPointer-test.js
+4
-1
@@ -24,7 +24,9 @@ beforeEach(() => {
24
act = require('jest-react').act;
25
26
Suspense = React.Suspense;
27
- SuspenseList = React.SuspenseList;
27
+ if (gate(flags => flags.enableSuspenseList)) {
28
+ SuspenseList = React.SuspenseList;
29
+ }
30
31
getCacheForType = React.unstable_getCacheForType;
32
@@ -197,6 +199,7 @@ test('warns in DEV if return pointer is inconsistent', async () => {
199
});
200
201
// @gate enableCache
202
+// @gate enableSuspenseList
203
test('regression (#20932): return pointer is correct before entering deleted tree', async () => {
204
// Based on a production bug. Designed to trigger a very specific
205
// implementation path.
packages/react-is/src/__tests__/ReactIs-test.js
+9
-3
@@ -12,6 +12,7 @@
12
let React;
13
let ReactDOM;
14
let ReactIs;
15
+let SuspenseList;
16
17
describe('ReactIs', () => {
18
beforeEach(() => {
@@ -20,6 +21,10 @@ describe('ReactIs', () => {
21
React = require('react');
22
ReactDOM = require('react-dom');
23
ReactIs = require('react-is');
24
+
25
+ if (gate(flags => flags.enableSuspenseList)) {
26
+ SuspenseList = React.SuspenseList;
27
+ }
28
});
29
30
it('should return undefined for unknown/invalid types', () => {
@@ -186,10 +191,11 @@ describe('ReactIs', () => {
191
expect(ReactIs.isSuspense(<div />)).toBe(false);
192
});
193
194
+ // @gate enableSuspenseList
195
it('should identify suspense list', () => {
190
- expect(ReactIs.isValidElementType(React.SuspenseList)).toBe(true);
191
- expect(ReactIs.typeOf(<React.SuspenseList />)).toBe(ReactIs.SuspenseList);
192
- expect(ReactIs.isSuspenseList(<React.SuspenseList />)).toBe(true);
196
+ expect(ReactIs.isValidElementType(SuspenseList)).toBe(true);
197
+ expect(ReactIs.typeOf(<SuspenseList />)).toBe(ReactIs.SuspenseList);
198
+ expect(ReactIs.isSuspenseList(<SuspenseList />)).toBe(true);
199
expect(ReactIs.isSuspenseList({type: ReactIs.SuspenseList})).toBe(false);
200
expect(ReactIs.isSuspenseList('React.SuspenseList')).toBe(false);
201
expect(ReactIs.isSuspenseList(<div />)).toBe(false);
packages/react-reconciler/src/__tests__/ReactContextPropagation-test.js
+4
-1
@@ -21,7 +21,9 @@ describe('ReactLazyContextPropagation', () => {
21
useState = React.useState;
22
useContext = React.useContext;
23
Suspense = React.Suspense;
24
- SuspenseList = React.SuspenseList;
24
+ if (gate(flags => flags.enableSuspenseList)) {
25
+ SuspenseList = React.SuspenseList;
26
+ }
27
28
getCacheForType = React.unstable_getCacheForType;
29
@@ -651,6 +653,7 @@ describe('ReactLazyContextPropagation', () => {
653
expect(root).toMatchRenderedOutput('BBB');
654
});
655
656
+ // @gate enableSuspenseList
657
test('contexts are propagated through SuspenseList', async () => {
658
// This kinda tests an implementation detail. SuspenseList has an early
659
// bailout that doesn't use `bailoutOnAlreadyFinishedWork`. It probably
packages/react-reconciler/src/__tests__/ReactHooksWithNoopRenderer-test.js
+5
-2
@@ -34,6 +34,7 @@ let forwardRef;
34
let memo;
35
let act;
36
let ContinuousEventPriority;
37
+let SuspenseList;
38
39
describe('ReactHooksWithNoopRenderer', () => {
40
beforeEach(() => {
@@ -60,6 +61,9 @@ describe('ReactHooksWithNoopRenderer', () => {
61
Suspense = React.Suspense;
62
ContinuousEventPriority = require('react-reconciler/constants')
63
.ContinuousEventPriority;
64
+ if (gate(flags => flags.enableSuspenseList)) {
65
+ SuspenseList = React.SuspenseList;
66
+ }
67
68
textCache = new Map();
69
@@ -4291,9 +4295,8 @@ describe('ReactHooksWithNoopRenderer', () => {
4295
]);
4296
});
4297
4298
+ // @gate enableSuspenseList
4299
it('regression: SuspenseList causes unmounts to be dropped on deletion', async () => {
4295
- const SuspenseList = React.SuspenseList;
4296
-
4300
function Row({label}) {
4301
useEffect(() => {
4302
Scheduler.unstable_yieldValue('Mount ' + label);
packages/react-reconciler/src/__tests__/ReactSuspenseList-test.js
+42
-1
@@ -16,7 +16,9 @@ describe('ReactSuspenseList', () => {
16
act = require('jest-react').act;
17
Profiler = React.Profiler;
18
Suspense = React.Suspense;
19
- SuspenseList = React.SuspenseList;
19
+ if (gate(flags => flags.enableSuspenseList)) {
20
+ SuspenseList = React.SuspenseList;
21
+ }
22
});
23
24
function Text(props) {
@@ -42,6 +44,7 @@ describe('ReactSuspenseList', () => {
44
return Component;
45
}
46
47
+ // @gate enableSuspenseList
48
it('warns if an unsupported revealOrder option is used', () => {
49
function Foo() {
50
return (
@@ -61,6 +64,7 @@ describe('ReactSuspenseList', () => {
64
]);
65
});
66
67
+ // @gate enableSuspenseList
68
it('warns if a upper case revealOrder option is used', () => {
69
function Foo() {
70
return (
@@ -80,6 +84,7 @@ describe('ReactSuspenseList', () => {
84
]);
85
});
86
87
+ // @gate enableSuspenseList
88
it('warns if a misspelled revealOrder option is used', () => {
89
function Foo() {
90
return (
@@ -100,6 +105,7 @@ describe('ReactSuspenseList', () => {
105
]);
106
});
107
108
+ // @gate enableSuspenseList
109
it('warns if a single element is passed to a "forwards" list', () => {
110
function Foo({children}) {
111
return <SuspenseList revealOrder="forwards">{children}</SuspenseList>;
@@ -132,6 +138,7 @@ describe('ReactSuspenseList', () => {
138
]);
139
});
140
141
+ // @gate enableSuspenseList
142
it('warns if a single fragment is passed to a "backwards" list', () => {
143
function Foo() {
144
return (
@@ -152,6 +159,7 @@ describe('ReactSuspenseList', () => {
159
]);
160
});
161
162
+ // @gate enableSuspenseList
163
it('warns if a nested array is passed to a "forwards" list', () => {
164
function Foo({items}) {
165
return (
@@ -179,6 +187,7 @@ describe('ReactSuspenseList', () => {
187
]);
188
});
189
190
+ // @gate enableSuspenseList
191
it('shows content independently by default', async () => {
192
const A = createAsyncText('A');
193
const B = createAsyncText('B');
@@ -245,6 +254,7 @@ describe('ReactSuspenseList', () => {
254
);
255
});
256
257
+ // @gate enableSuspenseList
258
it('shows content independently in legacy mode regardless of option', async () => {
259
const A = createAsyncText('A');
260
const B = createAsyncText('B');
@@ -315,6 +325,7 @@ describe('ReactSuspenseList', () => {
325
);
326
});
327
328
+ // @gate enableSuspenseList
329
it('displays all "together"', async () => {
330
const A = createAsyncText('A');
331
const B = createAsyncText('B');
@@ -384,6 +395,7 @@ describe('ReactSuspenseList', () => {
395
);
396
});
397
398
+ // @gate enableSuspenseList
399
it('displays all "together" even when nested as siblings', async () => {
400
const A = createAsyncText('A');
401
const B = createAsyncText('B');
@@ -469,6 +481,7 @@ describe('ReactSuspenseList', () => {
481
);
482
});
483
484
+ // @gate enableSuspenseList
485
it('displays all "together" in nested SuspenseLists', async () => {
486
const A = createAsyncText('A');
487
const B = createAsyncText('B');
@@ -530,6 +543,7 @@ describe('ReactSuspenseList', () => {
543
);
544
});
545
546
+ // @gate enableSuspenseList
547
it('displays all "together" in nested SuspenseLists where the inner is default', async () => {
548
const A = createAsyncText('A');
549
const B = createAsyncText('B');
@@ -589,6 +603,7 @@ describe('ReactSuspenseList', () => {
603
);
604
});
605
606
+ // @gate enableSuspenseList
607
it('displays all "together" during an update', async () => {
608
const A = createAsyncText('A');
609
const B = createAsyncText('B');
@@ -673,6 +688,7 @@ describe('ReactSuspenseList', () => {
688
);
689
});
690
691
+ // @gate enableSuspenseList
692
it('avoided boundaries can be coordinate with SuspenseList', async () => {
693
const A = createAsyncText('A');
694
const B = createAsyncText('B');
@@ -771,6 +787,7 @@ describe('ReactSuspenseList', () => {
787
);
788
});
789
790
+ // @gate enableSuspenseList
791
it('boundaries without fallbacks can be coordinate with SuspenseList', async () => {
792
const A = createAsyncText('A');
793
const B = createAsyncText('B');
@@ -854,6 +871,7 @@ describe('ReactSuspenseList', () => {
871
);
872
});
873
874
+ // @gate enableSuspenseList
875
it('displays each items in "forwards" order', async () => {
876
const A = createAsyncText('A');
877
const B = createAsyncText('B');
@@ -919,6 +937,7 @@ describe('ReactSuspenseList', () => {
937
);
938
});
939
940
+ // @gate enableSuspenseList
941
it('displays each items in "backwards" order', async () => {
942
const A = createAsyncText('A');
943
const B = createAsyncText('B');
@@ -984,6 +1003,7 @@ describe('ReactSuspenseList', () => {
1003
);
1004
});
1005
1006
+ // @gate enableSuspenseList
1007
it('displays added row at the top "together" and the bottom in "forwards" order', async () => {
1008
const A = createAsyncText('A');
1009
const B = createAsyncText('B');
@@ -1138,6 +1158,7 @@ describe('ReactSuspenseList', () => {
1158
);
1159
});
1160
1161
+ // @gate enableSuspenseList
1162
it('displays added row at the top "together" and the bottom in "backwards" order', async () => {
1163
const A = createAsyncText('A');
1164
const B = createAsyncText('B');
@@ -1322,6 +1343,7 @@ describe('ReactSuspenseList', () => {
1343
);
1344
});
1345
1346
+ // @gate enableSuspenseList
1347
it('switches to rendering fallbacks if the tail takes long CPU time', async () => {
1348
function Foo() {
1349
return (
@@ -1390,6 +1412,7 @@ describe('ReactSuspenseList', () => {
1412
);
1413
});
1414
1415
+ // @gate enableSuspenseList
1416
it('only shows one loading state at a time for "collapsed" tail insertions', async () => {
1417
const A = createAsyncText('A');
1418
const B = createAsyncText('B');
@@ -1459,6 +1482,7 @@ describe('ReactSuspenseList', () => {
1482
);
1483
});
1484
1485
+ // @gate enableSuspenseList
1486
it('warns if an unsupported tail option is used', () => {
1487
function Foo() {
1488
return (
@@ -1479,6 +1503,7 @@ describe('ReactSuspenseList', () => {
1503
]);
1504
});
1505
1506
+ // @gate enableSuspenseList
1507
it('warns if a tail option is used with "together"', () => {
1508
function Foo() {
1509
return (
@@ -1499,6 +1524,7 @@ describe('ReactSuspenseList', () => {
1524
]);
1525
});
1526
1527
+ // @gate enableSuspenseList
1528
it('renders one "collapsed" fallback even if CPU time elapsed', async () => {
1529
function Foo() {
1530
return (
@@ -1571,6 +1597,7 @@ describe('ReactSuspenseList', () => {
1597
);
1598
});
1599
1600
+ // @gate enableSuspenseList
1601
it('adding to the middle does not collapse insertions (forwards)', async () => {
1602
const A = createAsyncText('A');
1603
const B = createAsyncText('B');
@@ -1713,6 +1740,7 @@ describe('ReactSuspenseList', () => {
1740
);
1741
});
1742
1743
+ // @gate enableSuspenseList
1744
it('adding to the middle does not collapse insertions (backwards)', async () => {
1745
const A = createAsyncText('A');
1746
const B = createAsyncText('B');
@@ -1860,6 +1888,7 @@ describe('ReactSuspenseList', () => {
1888
);
1889
});
1890
1891
+ // @gate enableSuspenseList
1892
it('adding to the middle of committed tail does not collapse insertions', async () => {
1893
const A = createAsyncText('A');
1894
const B = createAsyncText('B');
@@ -2017,6 +2046,7 @@ describe('ReactSuspenseList', () => {
2046
);
2047
});
2048
2049
+ // @gate enableSuspenseList
2050
it('only shows no initial loading state "hidden" tail insertions', async () => {
2051
const A = createAsyncText('A');
2052
const B = createAsyncText('B');
@@ -2080,6 +2110,7 @@ describe('ReactSuspenseList', () => {
2110
);
2111
});
2112
2113
+ // @gate enableSuspenseList
2114
it('eventually resolves a nested forwards suspense list', async () => {
2115
const B = createAsyncText('B');
2116
@@ -2142,6 +2173,7 @@ describe('ReactSuspenseList', () => {
2173
);
2174
});
2175
2176
+ // @gate enableSuspenseList
2177
it('eventually resolves a nested forwards suspense list with a hidden tail', async () => {
2178
const B = createAsyncText('B');
2179
@@ -2188,6 +2220,7 @@ describe('ReactSuspenseList', () => {
2220
);
2221
});
2222
2223
+ // @gate enableSuspenseList
2224
it('eventually resolves two nested forwards suspense lists with a hidden tail', async () => {
2225
const B = createAsyncText('B');
2226
@@ -2255,6 +2288,7 @@ describe('ReactSuspenseList', () => {
2288
);
2289
});
2290
2291
+ // @gate enableSuspenseList
2292
it('can do unrelated adjacent updates', async () => {
2293
let updateAdjacent;
2294
function Adjacent() {
@@ -2301,6 +2335,7 @@ describe('ReactSuspenseList', () => {
2335
);
2336
});
2337
2338
+ // @gate enableSuspenseList
2339
it('is able to re-suspend the last rows during an update with hidden', async () => {
2340
const AsyncB = createAsyncText('B');
2341
@@ -2389,6 +2424,7 @@ describe('ReactSuspenseList', () => {
2424
expect(previousInst).toBe(setAsyncB);
2425
});
2426
2427
+ // @gate enableSuspenseList
2428
it('is able to re-suspend the last rows during an update with hidden', async () => {
2429
const AsyncB = createAsyncText('B');
2430
@@ -2477,6 +2513,7 @@ describe('ReactSuspenseList', () => {
2513
expect(previousInst).toBe(setAsyncB);
2514
});
2515
2516
+ // @gate enableSuspenseList
2517
it('is able to interrupt a partially rendered tree and continue later', async () => {
2518
const AsyncA = createAsyncText('A');
2519
@@ -2555,6 +2592,7 @@ describe('ReactSuspenseList', () => {
2592
);
2593
});
2594
2595
+ // @gate enableSuspenseList
2596
it('can resume class components when revealed together', async () => {
2597
const A = createAsyncText('A');
2598
const B = createAsyncText('B');
@@ -2617,6 +2655,7 @@ describe('ReactSuspenseList', () => {
2655
);
2656
});
2657
2658
+ // @gate enableSuspenseList
2659
it('should be able to progressively show CPU expensive rows with two pass rendering', async () => {
2660
function TwoPass({text}) {
2661
const [pass, setPass] = React.useState(0);
@@ -2687,6 +2726,7 @@ describe('ReactSuspenseList', () => {
2726
);
2727
});
2728
2729
+ // @gate enableSuspenseList
2730
it('should be able to progressively show rows with two pass rendering and visible', async () => {
2731
function TwoPass({text}) {
2732
const [pass, setPass] = React.useState(0);
@@ -2769,6 +2809,7 @@ describe('ReactSuspenseList', () => {
2809
});
2810
2811
// @gate enableProfilerTimer
2812
+ // @gate enableSuspenseList
2813
it('counts the actual duration when profiling a SuspenseList', async () => {
2814
// Order of parameters: id, phase, actualDuration, treeBaseDuration
2815
const onRender = jest.fn();
packages/react-server-dom-relay/src/__tests__/ReactFlightDOMRelay-test.internal.js
+6
-9
@@ -13,6 +13,7 @@ let ReactDOM;
13
let JSResourceReference;
14
let ReactDOMFlightRelayServer;
15
let ReactDOMFlightRelayClient;
16
+let SuspenseList;
17
18
describe('ReactFlightDOMRelay', () => {
19
beforeEach(() => {
@@ -24,6 +25,9 @@ describe('ReactFlightDOMRelay', () => {
25
ReactDOMFlightRelayServer = require('react-server-dom-relay/server');
26
ReactDOMFlightRelayClient = require('react-server-dom-relay');
27
JSResourceReference = require('JSResourceReference');
28
+ if (gate(flags => flags.enableSuspenseList)) {
29
+ SuspenseList = React.SuspenseList;
30
+ }
31
});
32
33
function readThrough(data) {
@@ -104,16 +108,9 @@ describe('ReactFlightDOMRelay', () => {
108
expect(container.innerHTML).toEqual('<span>Hello, Seb Smith</span>');
109
});
110
111
+ // @gate enableSuspenseList
112
it('can reasonably handle different element types', () => {
108
- const {
109
- forwardRef,
110
- memo,
111
- Fragment,
112
- StrictMode,
113
- Profiler,
114
- Suspense,
115
- SuspenseList,
116
- } = React;
113
+ const {forwardRef, memo, Fragment, StrictMode, Profiler, Suspense} = React;
114
115
const Inner = memo(
116
forwardRef((props, ref) => {
packages/react/index.stable.js
-1
@@ -17,7 +17,6 @@ export {
17
PureComponent,
18
StrictMode,
19
Suspense,
20
- SuspenseList,
20
cloneElement,
21
createContext,
22
createElement,
scripts/jest/TestFlags.js
+1
@@ -86,6 +86,7 @@ function getTestFlags() {
86
87
// This isn't a flag, just a useful alias for tests.
88
enableUseSyncExternalStoreShim: !__VARIANT__,
89
+ enableSuspenseList: releaseChannel === 'experimental' || www,
90
91
// If there's a naming conflict between scheduler and React feature flags, the
92
// React ones take precedence.