@samitouri / QOS-React-2 / commits / 83564712b6

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.