diff --git a/packages/react-dom/src/__tests__/ReactDOMFizzServer-test.js b/packages/react-dom/src/__tests__/ReactDOMFizzServer-test.js
index 8c282e1bb730e..6d169f9fbbb17 100644
--- a/packages/react-dom/src/__tests__/ReactDOMFizzServer-test.js
+++ b/packages/react-dom/src/__tests__/ReactDOMFizzServer-test.js
@@ -42,7 +42,9 @@ describe('ReactDOMFizzServer', () => {
}
Stream = require('stream');
Suspense = React.Suspense;
- SuspenseList = React.SuspenseList;
+ if (gate(flags => flags.enableSuspenseList)) {
+ SuspenseList = React.SuspenseList;
+ }
PropTypes = require('prop-types');
@@ -656,6 +658,7 @@ describe('ReactDOMFizzServer', () => {
expect(ref.current).toBe(b);
});
+ // @gate enableSuspenseList
// @gate experimental
it('shows inserted items before pending in a SuspenseList as fallbacks while hydrating', async () => {
const ref = React.createRef();
diff --git a/packages/react-dom/src/__tests__/ReactDOMServerPartialHydration-test.internal.js b/packages/react-dom/src/__tests__/ReactDOMServerPartialHydration-test.internal.js
index 9aab3bf94536f..0e1e220e4fe07 100644
--- a/packages/react-dom/src/__tests__/ReactDOMServerPartialHydration-test.internal.js
+++ b/packages/react-dom/src/__tests__/ReactDOMServerPartialHydration-test.internal.js
@@ -84,7 +84,9 @@ describe('ReactDOMServerPartialHydration', () => {
ReactDOMServer = require('react-dom/server');
Scheduler = require('scheduler');
Suspense = React.Suspense;
- SuspenseList = React.SuspenseList;
+ if (gate(flags => flags.enableSuspenseList)) {
+ SuspenseList = React.SuspenseList;
+ }
IdleEventPriority = require('react-reconciler/constants').IdleEventPriority;
});
@@ -1545,6 +1547,7 @@ describe('ReactDOMServerPartialHydration', () => {
expect(ref.current).toBe(span);
});
+ // @gate enableSuspenseList
it('shows inserted items in a SuspenseList before content is hydrated', async () => {
let suspend = false;
let resolve;
@@ -1630,6 +1633,7 @@ describe('ReactDOMServerPartialHydration', () => {
expect(ref.current).toBe(spanB);
});
+ // @gate enableSuspenseList
it('shows is able to hydrate boundaries even if others in a list are pending', async () => {
let suspend = false;
let resolve;
@@ -1704,7 +1708,7 @@ describe('ReactDOMServerPartialHydration', () => {
expect(container.textContent).toBe('ALoading B');
});
- // @gate experimental || www
+ // @gate enableSuspenseList
it('clears server boundaries when SuspenseList runs out of time hydrating', async () => {
let suspend = false;
let resolve;
@@ -1807,6 +1811,7 @@ describe('ReactDOMServerPartialHydration', () => {
expect(ref.current).toBe(b);
});
+ // @gate enableSuspenseList
it('clears server boundaries when SuspenseList suspends last row hydrating', async () => {
let suspend = false;
let resolve;
diff --git a/packages/react-dom/src/__tests__/ReactDOMServerSuspense-test.internal.js b/packages/react-dom/src/__tests__/ReactDOMServerSuspense-test.internal.js
index 60ab2edbdcf2b..c2eb630e52acd 100644
--- a/packages/react-dom/src/__tests__/ReactDOMServerSuspense-test.internal.js
+++ b/packages/react-dom/src/__tests__/ReactDOMServerSuspense-test.internal.js
@@ -16,6 +16,7 @@ let ReactDOM;
let ReactDOMServer;
let ReactTestUtils;
let act;
+let SuspenseList;
function initModules() {
// Reset warning cache.
@@ -26,6 +27,9 @@ function initModules() {
ReactDOMServer = require('react-dom/server');
ReactTestUtils = require('react-dom/test-utils');
act = require('jest-react').act;
+ if (gate(flags => flags.enableSuspenseList)) {
+ SuspenseList = React.SuspenseList;
+ }
// Make them available to the helpers.
return {
@@ -137,16 +141,17 @@ describe('ReactDOMServerSuspense', () => {
);
});
+ // @gate enableSuspenseList
it('server renders a SuspenseList component and its children', async () => {
const example = (
-
+
A
B
-
+
);
const element = await serverRender(example);
const parent = element.parentNode;
diff --git a/packages/react-dom/src/__tests__/ReactWrongReturnPointer-test.js b/packages/react-dom/src/__tests__/ReactWrongReturnPointer-test.js
index 66cf344814dc8..ca5c9fac51624 100644
--- a/packages/react-dom/src/__tests__/ReactWrongReturnPointer-test.js
+++ b/packages/react-dom/src/__tests__/ReactWrongReturnPointer-test.js
@@ -24,7 +24,9 @@ beforeEach(() => {
act = require('jest-react').act;
Suspense = React.Suspense;
- SuspenseList = React.SuspenseList;
+ if (gate(flags => flags.enableSuspenseList)) {
+ SuspenseList = React.SuspenseList;
+ }
getCacheForType = React.unstable_getCacheForType;
@@ -197,6 +199,7 @@ test('warns in DEV if return pointer is inconsistent', async () => {
});
// @gate enableCache
+// @gate enableSuspenseList
test('regression (#20932): return pointer is correct before entering deleted tree', async () => {
// Based on a production bug. Designed to trigger a very specific
// implementation path.
diff --git a/packages/react-is/src/__tests__/ReactIs-test.js b/packages/react-is/src/__tests__/ReactIs-test.js
index b6adc66b00b53..6a5976e97194f 100644
--- a/packages/react-is/src/__tests__/ReactIs-test.js
+++ b/packages/react-is/src/__tests__/ReactIs-test.js
@@ -12,6 +12,7 @@
let React;
let ReactDOM;
let ReactIs;
+let SuspenseList;
describe('ReactIs', () => {
beforeEach(() => {
@@ -20,6 +21,10 @@ describe('ReactIs', () => {
React = require('react');
ReactDOM = require('react-dom');
ReactIs = require('react-is');
+
+ if (gate(flags => flags.enableSuspenseList)) {
+ SuspenseList = React.SuspenseList;
+ }
});
it('should return undefined for unknown/invalid types', () => {
@@ -186,10 +191,11 @@ describe('ReactIs', () => {
expect(ReactIs.isSuspense(
)).toBe(false);
});
+ // @gate enableSuspenseList
it('should identify suspense list', () => {
- expect(ReactIs.isValidElementType(React.SuspenseList)).toBe(true);
- expect(ReactIs.typeOf()).toBe(ReactIs.SuspenseList);
- expect(ReactIs.isSuspenseList()).toBe(true);
+ expect(ReactIs.isValidElementType(SuspenseList)).toBe(true);
+ expect(ReactIs.typeOf()).toBe(ReactIs.SuspenseList);
+ expect(ReactIs.isSuspenseList()).toBe(true);
expect(ReactIs.isSuspenseList({type: ReactIs.SuspenseList})).toBe(false);
expect(ReactIs.isSuspenseList('React.SuspenseList')).toBe(false);
expect(ReactIs.isSuspenseList()).toBe(false);
diff --git a/packages/react-reconciler/src/__tests__/ReactContextPropagation-test.js b/packages/react-reconciler/src/__tests__/ReactContextPropagation-test.js
index f568e40ccad7c..24e57002ef303 100644
--- a/packages/react-reconciler/src/__tests__/ReactContextPropagation-test.js
+++ b/packages/react-reconciler/src/__tests__/ReactContextPropagation-test.js
@@ -21,7 +21,9 @@ describe('ReactLazyContextPropagation', () => {
useState = React.useState;
useContext = React.useContext;
Suspense = React.Suspense;
- SuspenseList = React.SuspenseList;
+ if (gate(flags => flags.enableSuspenseList)) {
+ SuspenseList = React.SuspenseList;
+ }
getCacheForType = React.unstable_getCacheForType;
@@ -651,6 +653,7 @@ describe('ReactLazyContextPropagation', () => {
expect(root).toMatchRenderedOutput('BBB');
});
+ // @gate enableSuspenseList
test('contexts are propagated through SuspenseList', async () => {
// This kinda tests an implementation detail. SuspenseList has an early
// bailout that doesn't use `bailoutOnAlreadyFinishedWork`. It probably
diff --git a/packages/react-reconciler/src/__tests__/ReactHooksWithNoopRenderer-test.js b/packages/react-reconciler/src/__tests__/ReactHooksWithNoopRenderer-test.js
index dae01c885fe5a..811be09639158 100644
--- a/packages/react-reconciler/src/__tests__/ReactHooksWithNoopRenderer-test.js
+++ b/packages/react-reconciler/src/__tests__/ReactHooksWithNoopRenderer-test.js
@@ -34,6 +34,7 @@ let forwardRef;
let memo;
let act;
let ContinuousEventPriority;
+let SuspenseList;
describe('ReactHooksWithNoopRenderer', () => {
beforeEach(() => {
@@ -60,6 +61,9 @@ describe('ReactHooksWithNoopRenderer', () => {
Suspense = React.Suspense;
ContinuousEventPriority = require('react-reconciler/constants')
.ContinuousEventPriority;
+ if (gate(flags => flags.enableSuspenseList)) {
+ SuspenseList = React.SuspenseList;
+ }
textCache = new Map();
@@ -4291,9 +4295,8 @@ describe('ReactHooksWithNoopRenderer', () => {
]);
});
+ // @gate enableSuspenseList
it('regression: SuspenseList causes unmounts to be dropped on deletion', async () => {
- const SuspenseList = React.SuspenseList;
-
function Row({label}) {
useEffect(() => {
Scheduler.unstable_yieldValue('Mount ' + label);
diff --git a/packages/react-reconciler/src/__tests__/ReactSuspenseList-test.js b/packages/react-reconciler/src/__tests__/ReactSuspenseList-test.js
index e969a59c4f60f..d357094f78c96 100644
--- a/packages/react-reconciler/src/__tests__/ReactSuspenseList-test.js
+++ b/packages/react-reconciler/src/__tests__/ReactSuspenseList-test.js
@@ -16,7 +16,9 @@ describe('ReactSuspenseList', () => {
act = require('jest-react').act;
Profiler = React.Profiler;
Suspense = React.Suspense;
- SuspenseList = React.SuspenseList;
+ if (gate(flags => flags.enableSuspenseList)) {
+ SuspenseList = React.SuspenseList;
+ }
});
function Text(props) {
@@ -42,6 +44,7 @@ describe('ReactSuspenseList', () => {
return Component;
}
+ // @gate enableSuspenseList
it('warns if an unsupported revealOrder option is used', () => {
function Foo() {
return (
@@ -61,6 +64,7 @@ describe('ReactSuspenseList', () => {
]);
});
+ // @gate enableSuspenseList
it('warns if a upper case revealOrder option is used', () => {
function Foo() {
return (
@@ -80,6 +84,7 @@ describe('ReactSuspenseList', () => {
]);
});
+ // @gate enableSuspenseList
it('warns if a misspelled revealOrder option is used', () => {
function Foo() {
return (
@@ -100,6 +105,7 @@ describe('ReactSuspenseList', () => {
]);
});
+ // @gate enableSuspenseList
it('warns if a single element is passed to a "forwards" list', () => {
function Foo({children}) {
return {children};
@@ -132,6 +138,7 @@ describe('ReactSuspenseList', () => {
]);
});
+ // @gate enableSuspenseList
it('warns if a single fragment is passed to a "backwards" list', () => {
function Foo() {
return (
@@ -152,6 +159,7 @@ describe('ReactSuspenseList', () => {
]);
});
+ // @gate enableSuspenseList
it('warns if a nested array is passed to a "forwards" list', () => {
function Foo({items}) {
return (
@@ -179,6 +187,7 @@ describe('ReactSuspenseList', () => {
]);
});
+ // @gate enableSuspenseList
it('shows content independently by default', async () => {
const A = createAsyncText('A');
const B = createAsyncText('B');
@@ -245,6 +254,7 @@ describe('ReactSuspenseList', () => {
);
});
+ // @gate enableSuspenseList
it('shows content independently in legacy mode regardless of option', async () => {
const A = createAsyncText('A');
const B = createAsyncText('B');
@@ -315,6 +325,7 @@ describe('ReactSuspenseList', () => {
);
});
+ // @gate enableSuspenseList
it('displays all "together"', async () => {
const A = createAsyncText('A');
const B = createAsyncText('B');
@@ -384,6 +395,7 @@ describe('ReactSuspenseList', () => {
);
});
+ // @gate enableSuspenseList
it('displays all "together" even when nested as siblings', async () => {
const A = createAsyncText('A');
const B = createAsyncText('B');
@@ -469,6 +481,7 @@ describe('ReactSuspenseList', () => {
);
});
+ // @gate enableSuspenseList
it('displays all "together" in nested SuspenseLists', async () => {
const A = createAsyncText('A');
const B = createAsyncText('B');
@@ -530,6 +543,7 @@ describe('ReactSuspenseList', () => {
);
});
+ // @gate enableSuspenseList
it('displays all "together" in nested SuspenseLists where the inner is default', async () => {
const A = createAsyncText('A');
const B = createAsyncText('B');
@@ -589,6 +603,7 @@ describe('ReactSuspenseList', () => {
);
});
+ // @gate enableSuspenseList
it('displays all "together" during an update', async () => {
const A = createAsyncText('A');
const B = createAsyncText('B');
@@ -673,6 +688,7 @@ describe('ReactSuspenseList', () => {
);
});
+ // @gate enableSuspenseList
it('avoided boundaries can be coordinate with SuspenseList', async () => {
const A = createAsyncText('A');
const B = createAsyncText('B');
@@ -771,6 +787,7 @@ describe('ReactSuspenseList', () => {
);
});
+ // @gate enableSuspenseList
it('boundaries without fallbacks can be coordinate with SuspenseList', async () => {
const A = createAsyncText('A');
const B = createAsyncText('B');
@@ -854,6 +871,7 @@ describe('ReactSuspenseList', () => {
);
});
+ // @gate enableSuspenseList
it('displays each items in "forwards" order', async () => {
const A = createAsyncText('A');
const B = createAsyncText('B');
@@ -919,6 +937,7 @@ describe('ReactSuspenseList', () => {
);
});
+ // @gate enableSuspenseList
it('displays each items in "backwards" order', async () => {
const A = createAsyncText('A');
const B = createAsyncText('B');
@@ -984,6 +1003,7 @@ describe('ReactSuspenseList', () => {
);
});
+ // @gate enableSuspenseList
it('displays added row at the top "together" and the bottom in "forwards" order', async () => {
const A = createAsyncText('A');
const B = createAsyncText('B');
@@ -1138,6 +1158,7 @@ describe('ReactSuspenseList', () => {
);
});
+ // @gate enableSuspenseList
it('displays added row at the top "together" and the bottom in "backwards" order', async () => {
const A = createAsyncText('A');
const B = createAsyncText('B');
@@ -1322,6 +1343,7 @@ describe('ReactSuspenseList', () => {
);
});
+ // @gate enableSuspenseList
it('switches to rendering fallbacks if the tail takes long CPU time', async () => {
function Foo() {
return (
@@ -1390,6 +1412,7 @@ describe('ReactSuspenseList', () => {
);
});
+ // @gate enableSuspenseList
it('only shows one loading state at a time for "collapsed" tail insertions', async () => {
const A = createAsyncText('A');
const B = createAsyncText('B');
@@ -1459,6 +1482,7 @@ describe('ReactSuspenseList', () => {
);
});
+ // @gate enableSuspenseList
it('warns if an unsupported tail option is used', () => {
function Foo() {
return (
@@ -1479,6 +1503,7 @@ describe('ReactSuspenseList', () => {
]);
});
+ // @gate enableSuspenseList
it('warns if a tail option is used with "together"', () => {
function Foo() {
return (
@@ -1499,6 +1524,7 @@ describe('ReactSuspenseList', () => {
]);
});
+ // @gate enableSuspenseList
it('renders one "collapsed" fallback even if CPU time elapsed', async () => {
function Foo() {
return (
@@ -1571,6 +1597,7 @@ describe('ReactSuspenseList', () => {
);
});
+ // @gate enableSuspenseList
it('adding to the middle does not collapse insertions (forwards)', async () => {
const A = createAsyncText('A');
const B = createAsyncText('B');
@@ -1713,6 +1740,7 @@ describe('ReactSuspenseList', () => {
);
});
+ // @gate enableSuspenseList
it('adding to the middle does not collapse insertions (backwards)', async () => {
const A = createAsyncText('A');
const B = createAsyncText('B');
@@ -1860,6 +1888,7 @@ describe('ReactSuspenseList', () => {
);
});
+ // @gate enableSuspenseList
it('adding to the middle of committed tail does not collapse insertions', async () => {
const A = createAsyncText('A');
const B = createAsyncText('B');
@@ -2017,6 +2046,7 @@ describe('ReactSuspenseList', () => {
);
});
+ // @gate enableSuspenseList
it('only shows no initial loading state "hidden" tail insertions', async () => {
const A = createAsyncText('A');
const B = createAsyncText('B');
@@ -2080,6 +2110,7 @@ describe('ReactSuspenseList', () => {
);
});
+ // @gate enableSuspenseList
it('eventually resolves a nested forwards suspense list', async () => {
const B = createAsyncText('B');
@@ -2142,6 +2173,7 @@ describe('ReactSuspenseList', () => {
);
});
+ // @gate enableSuspenseList
it('eventually resolves a nested forwards suspense list with a hidden tail', async () => {
const B = createAsyncText('B');
@@ -2188,6 +2220,7 @@ describe('ReactSuspenseList', () => {
);
});
+ // @gate enableSuspenseList
it('eventually resolves two nested forwards suspense lists with a hidden tail', async () => {
const B = createAsyncText('B');
@@ -2255,6 +2288,7 @@ describe('ReactSuspenseList', () => {
);
});
+ // @gate enableSuspenseList
it('can do unrelated adjacent updates', async () => {
let updateAdjacent;
function Adjacent() {
@@ -2301,6 +2335,7 @@ describe('ReactSuspenseList', () => {
);
});
+ // @gate enableSuspenseList
it('is able to re-suspend the last rows during an update with hidden', async () => {
const AsyncB = createAsyncText('B');
@@ -2389,6 +2424,7 @@ describe('ReactSuspenseList', () => {
expect(previousInst).toBe(setAsyncB);
});
+ // @gate enableSuspenseList
it('is able to re-suspend the last rows during an update with hidden', async () => {
const AsyncB = createAsyncText('B');
@@ -2477,6 +2513,7 @@ describe('ReactSuspenseList', () => {
expect(previousInst).toBe(setAsyncB);
});
+ // @gate enableSuspenseList
it('is able to interrupt a partially rendered tree and continue later', async () => {
const AsyncA = createAsyncText('A');
@@ -2555,6 +2592,7 @@ describe('ReactSuspenseList', () => {
);
});
+ // @gate enableSuspenseList
it('can resume class components when revealed together', async () => {
const A = createAsyncText('A');
const B = createAsyncText('B');
@@ -2617,6 +2655,7 @@ describe('ReactSuspenseList', () => {
);
});
+ // @gate enableSuspenseList
it('should be able to progressively show CPU expensive rows with two pass rendering', async () => {
function TwoPass({text}) {
const [pass, setPass] = React.useState(0);
@@ -2687,6 +2726,7 @@ describe('ReactSuspenseList', () => {
);
});
+ // @gate enableSuspenseList
it('should be able to progressively show rows with two pass rendering and visible', async () => {
function TwoPass({text}) {
const [pass, setPass] = React.useState(0);
@@ -2769,6 +2809,7 @@ describe('ReactSuspenseList', () => {
});
// @gate enableProfilerTimer
+ // @gate enableSuspenseList
it('counts the actual duration when profiling a SuspenseList', async () => {
// Order of parameters: id, phase, actualDuration, treeBaseDuration
const onRender = jest.fn();
diff --git a/packages/react-server-dom-relay/src/__tests__/ReactFlightDOMRelay-test.internal.js b/packages/react-server-dom-relay/src/__tests__/ReactFlightDOMRelay-test.internal.js
index 75ac4ef8850a2..11d25a5a11966 100644
--- a/packages/react-server-dom-relay/src/__tests__/ReactFlightDOMRelay-test.internal.js
+++ b/packages/react-server-dom-relay/src/__tests__/ReactFlightDOMRelay-test.internal.js
@@ -13,6 +13,7 @@ let ReactDOM;
let JSResourceReference;
let ReactDOMFlightRelayServer;
let ReactDOMFlightRelayClient;
+let SuspenseList;
describe('ReactFlightDOMRelay', () => {
beforeEach(() => {
@@ -24,6 +25,9 @@ describe('ReactFlightDOMRelay', () => {
ReactDOMFlightRelayServer = require('react-server-dom-relay/server');
ReactDOMFlightRelayClient = require('react-server-dom-relay');
JSResourceReference = require('JSResourceReference');
+ if (gate(flags => flags.enableSuspenseList)) {
+ SuspenseList = React.SuspenseList;
+ }
});
function readThrough(data) {
@@ -104,16 +108,9 @@ describe('ReactFlightDOMRelay', () => {
expect(container.innerHTML).toEqual('Hello, Seb Smith');
});
+ // @gate enableSuspenseList
it('can reasonably handle different element types', () => {
- const {
- forwardRef,
- memo,
- Fragment,
- StrictMode,
- Profiler,
- Suspense,
- SuspenseList,
- } = React;
+ const {forwardRef, memo, Fragment, StrictMode, Profiler, Suspense} = React;
const Inner = memo(
forwardRef((props, ref) => {
diff --git a/packages/react/index.stable.js b/packages/react/index.stable.js
index 234ec8336a9ed..b0f6f409bb0e1 100644
--- a/packages/react/index.stable.js
+++ b/packages/react/index.stable.js
@@ -17,7 +17,6 @@ export {
PureComponent,
StrictMode,
Suspense,
- SuspenseList,
cloneElement,
createContext,
createElement,
diff --git a/scripts/jest/TestFlags.js b/scripts/jest/TestFlags.js
index af83baf0f3ec9..e76a93638336f 100644
--- a/scripts/jest/TestFlags.js
+++ b/scripts/jest/TestFlags.js
@@ -86,6 +86,7 @@ function getTestFlags() {
// This isn't a flag, just a useful alias for tests.
enableUseSyncExternalStoreShim: !__VARIANT__,
+ enableSuspenseList: releaseChannel === 'experimental' || www,
// If there's a naming conflict between scheduler and React feature flags, the
// React ones take precedence.