@@ -92,7 +92,7 @@ describe('ReactDOMSuspensePlaceholder', () => {
expect(divs[1].current.style.display).toEqual('none');
expect(divs[2].current.style.display).toEqual('none');
- await advanceTimers(1000);
+ await advanceTimers(500);
expect(divs[0].current.style.display).toEqual('');
expect(divs[1].current.style.display).toEqual('');
@@ -103,9 +103,9 @@ describe('ReactDOMSuspensePlaceholder', () => {
it('hides and unhides timed out text nodes', async () => {
function App() {
return (
-
}>
+
}>
-
+
);
@@ -113,7 +113,7 @@ describe('ReactDOMSuspensePlaceholder', () => {
ReactDOM.render(
, container);
expect(container.textContent).toEqual('Loading...');
- await advanceTimers(1000);
+ await advanceTimers(500);
expect(container.textContent).toEqual('ABC');
});
@@ -137,10 +137,10 @@ describe('ReactDOMSuspensePlaceholder', () => {
function App() {
return (
-
}>
+
}>
Sibling
-
+
);
@@ -158,7 +158,7 @@ describe('ReactDOMSuspensePlaceholder', () => {
'
SiblingLoading...',
);
- await advanceTimers(1000);
+ await advanceTimers(500);
expect(container.innerHTML).toEqual(
'
SiblingAsync',
diff --git a/packages/react-reconciler/src/ReactFiberBeginWork.js b/packages/react-reconciler/src/ReactFiberBeginWork.js
index 3c5e0fddae0e1..695822811e2f8 100644
--- a/packages/react-reconciler/src/ReactFiberBeginWork.js
+++ b/packages/react-reconciler/src/ReactFiberBeginWork.js
@@ -156,6 +156,7 @@ let didWarnAboutContextTypeOnFunctionComponent;
let didWarnAboutGetDerivedStateOnFunctionComponent;
let didWarnAboutFunctionRefs;
export let didWarnAboutReassigningProps;
+let didWarnAboutMaxDuration;
if (__DEV__) {
didWarnAboutBadClass = {};
@@ -164,6 +165,7 @@ if (__DEV__) {
didWarnAboutGetDerivedStateOnFunctionComponent = {};
didWarnAboutFunctionRefs = {};
didWarnAboutReassigningProps = false;
+ didWarnAboutMaxDuration = false;
}
export function reconcileChildren(
@@ -1408,6 +1410,19 @@ function updateSuspenseComponent(
workInProgress.effectTag &= ~DidCapture;
}
+ if (__DEV__) {
+ if ('maxDuration' in nextProps) {
+ if (!didWarnAboutMaxDuration) {
+ didWarnAboutMaxDuration = true;
+ warning(
+ false,
+ 'maxDuration has been removed from React. ' +
+ 'Remove the maxDuration prop.',
+ );
+ }
+ }
+ }
+
// This next part is a bit confusing. If the children timeout, we switch to
// showing the fallback children in place of the "primary" children.
// However, we don't want to delete the primary children because then their
diff --git a/packages/react-reconciler/src/ReactFiberUnwindWork.js b/packages/react-reconciler/src/ReactFiberUnwindWork.js
index 2d76a3b6b5307..fe732abaffa5f 100644
--- a/packages/react-reconciler/src/ReactFiberUnwindWork.js
+++ b/packages/react-reconciler/src/ReactFiberUnwindWork.js
@@ -229,16 +229,12 @@ function throwException(
break;
}
}
- let timeoutPropMs = workInProgress.pendingProps.maxDuration;
- if (typeof timeoutPropMs === 'number') {
- if (timeoutPropMs <= 0) {
- earliestTimeoutMs = 0;
- } else if (
- earliestTimeoutMs === -1 ||
- timeoutPropMs < earliestTimeoutMs
- ) {
- earliestTimeoutMs = timeoutPropMs;
- }
+ const defaultSuspenseTimeout = 150;
+ if (
+ earliestTimeoutMs === -1 ||
+ defaultSuspenseTimeout < earliestTimeoutMs
+ ) {
+ earliestTimeoutMs = defaultSuspenseTimeout;
}
}
// If there is a DehydratedSuspenseComponent we don't have to do anything because
diff --git a/packages/react-reconciler/src/__tests__/ReactLazy-test.internal.js b/packages/react-reconciler/src/__tests__/ReactLazy-test.internal.js
index 4e80064514006..cd91c40574072 100644
--- a/packages/react-reconciler/src/__tests__/ReactLazy-test.internal.js
+++ b/packages/react-reconciler/src/__tests__/ReactLazy-test.internal.js
@@ -119,8 +119,8 @@ describe('ReactLazy', () => {
return
;
}
- const promiseForFoo = delay(1000).then(() => fakeImport(Foo));
- const promiseForBar = delay(2000).then(() => fakeImport(Bar));
+ const promiseForFoo = delay(100).then(() => fakeImport(Foo));
+ const promiseForBar = delay(500).then(() => fakeImport(Bar));
const LazyFoo = lazy(() => promiseForFoo);
const LazyBar = lazy(() => promiseForBar);
@@ -138,13 +138,13 @@ describe('ReactLazy', () => {
expect(Scheduler).toFlushAndYield(['Loading...']);
expect(root).toMatchRenderedOutput(null);
- jest.advanceTimersByTime(1000);
+ jest.advanceTimersByTime(100);
await promiseForFoo;
expect(Scheduler).toFlushAndYield(['Foo', 'Loading...']);
expect(root).toMatchRenderedOutput(null);
- jest.advanceTimersByTime(1000);
+ jest.advanceTimersByTime(500);
await promiseForBar;
expect(Scheduler).toFlushAndYield(['Foo', 'Bar']);
diff --git a/packages/react-reconciler/src/__tests__/ReactSuspense-test.internal.js b/packages/react-reconciler/src/__tests__/ReactSuspense-test.internal.js
index 913cbfbd32c4b..14fe319b7fe43 100644
--- a/packages/react-reconciler/src/__tests__/ReactSuspense-test.internal.js
+++ b/packages/react-reconciler/src/__tests__/ReactSuspense-test.internal.js
@@ -140,10 +140,10 @@ describe('ReactSuspense', () => {
// Render two sibling Suspense components
const root = ReactTestRenderer.create(
- }>
+ }>
- }>
+ }>
,
@@ -211,7 +211,7 @@ describe('ReactSuspense', () => {
}
const root = ReactTestRenderer.create(
-
}>
+
}>
,
@@ -272,7 +272,7 @@ describe('ReactSuspense', () => {
it('only captures if `fallback` is defined', () => {
const root = ReactTestRenderer.create(
}>
-
+
,
@@ -368,9 +368,7 @@ describe('ReactSuspense', () => {
function App() {
return (
-
}>
+
}>
@@ -631,7 +629,7 @@ describe('ReactSuspense', () => {
function App(props) {
return (
-
}>
+
}>
);
@@ -681,7 +679,7 @@ describe('ReactSuspense', () => {
function App(props) {
return (
-
}>
+
}>
@@ -726,7 +724,7 @@ describe('ReactSuspense', () => {
it('does not get stuck with fallback in concurrent mode for a large delay', () => {
function App(props) {
return (
-
}>
+
}>
diff --git a/packages/react-reconciler/src/__tests__/ReactSuspenseFuzz-test.internal.js b/packages/react-reconciler/src/__tests__/ReactSuspenseFuzz-test.internal.js
index 22c2c051af38d..5c0323a657494 100644
--- a/packages/react-reconciler/src/__tests__/ReactSuspenseFuzz-test.internal.js
+++ b/packages/react-reconciler/src/__tests__/ReactSuspenseFuzz-test.internal.js
@@ -268,11 +268,6 @@ describe('ReactSuspenseFuzz', () => {
remainingElements--;
const children = createRandomChildren(3);
- const maxDuration = pickRandomWeighted(rand, [
- {value: undefined, weight: 1},
- {value: rand.intBetween(0, 5000), weight: 1},
- ]);
-
const fallbackType = pickRandomWeighted(rand, [
{value: 'none', weight: 1},
{value: 'normal', weight: 1},
@@ -290,11 +285,7 @@ describe('ReactSuspenseFuzz', () => {
);
}
- return React.createElement(
- Suspense,
- {maxDuration, fallback},
- ...children,
- );
+ return React.createElement(Suspense, {fallback}, ...children);
}
case 'return':
default:
diff --git a/packages/react-reconciler/src/__tests__/ReactSuspensePlaceholder-test.internal.js b/packages/react-reconciler/src/__tests__/ReactSuspensePlaceholder-test.internal.js
index bb838c558a765..89f25adcd3cdd 100644
--- a/packages/react-reconciler/src/__tests__/ReactSuspensePlaceholder-test.internal.js
+++ b/packages/react-reconciler/src/__tests__/ReactSuspensePlaceholder-test.internal.js
@@ -110,7 +110,7 @@ describe('ReactSuspensePlaceholder', () => {
function App(props) {
return (
-
}>
+
}>
@@ -176,7 +176,7 @@ describe('ReactSuspensePlaceholder', () => {
it('times out text nodes', async () => {
function App(props) {
return (
- }>
+ }>
@@ -225,7 +225,7 @@ describe('ReactSuspensePlaceholder', () => {
// uppercase is a special type that causes React Noop to render child
// text nodes as uppercase.
- }>
+ }>
@@ -293,7 +293,7 @@ describe('ReactSuspensePlaceholder', () => {
Scheduler.yieldValue('App');
return (
- }>
+ }>
{shouldSuspend && }
diff --git a/packages/react-reconciler/src/__tests__/ReactSuspenseWithNoopRenderer-test.internal.js b/packages/react-reconciler/src/__tests__/ReactSuspenseWithNoopRenderer-test.internal.js
index f438dbb2dafd4..18d85fadb0b1d 100644
--- a/packages/react-reconciler/src/__tests__/ReactSuspenseWithNoopRenderer-test.internal.js
+++ b/packages/react-reconciler/src/__tests__/ReactSuspenseWithNoopRenderer-test.internal.js
@@ -89,6 +89,25 @@ describe('ReactSuspenseWithNoopRenderer', () => {
}
}
+ it('warns if the deprecated maxDuration option is used', () => {
+ function Foo() {
+ return (
+
+ ;
+
+ );
+ }
+
+ ReactNoop.render();
+
+ expect(() => Scheduler.flushAll()).toWarnDev([
+ 'Warning: maxDuration has been removed from React. ' +
+ 'Remove the maxDuration prop.' +
+ '\n in Suspense (at **)' +
+ '\n in Foo (at **)',
+ ]);
+ });
+
it('suspends rendering and continues later', async () => {
function Bar(props) {
Scheduler.yieldValue('Bar');
@@ -137,10 +156,10 @@ describe('ReactSuspenseWithNoopRenderer', () => {
// Render two sibling Suspense components
ReactNoop.render(
- }>
+ }>
- }>
+ }>
,
@@ -289,7 +308,7 @@ describe('ReactSuspenseWithNoopRenderer', () => {
const errorBoundary = React.createRef();
function App() {
return (
- }>
+ }>
@@ -369,6 +388,9 @@ describe('ReactSuspenseWithNoopRenderer', () => {
});
it('keeps working on lower priority work after being pinged', async () => {
+ // Advance the virtual time so that we're close to the edge of a bucket.
+ ReactNoop.expire(149);
+
function App(props) {
return (
}>
@@ -382,8 +404,9 @@ describe('ReactSuspenseWithNoopRenderer', () => {
expect(Scheduler).toFlushAndYield(['Suspend! [A]', 'Loading...']);
expect(ReactNoop.getChildren()).toEqual([]);
- // Advance React's virtual time by enough to fall into a new async bucket.
- ReactNoop.expire(1200);
+ // Advance React's virtual time by enough to fall into a new async bucket,
+ // but not enough to expire the suspense timeout.
+ ReactNoop.expire(120);
ReactNoop.render();
expect(Scheduler).toFlushAndYield(['Suspend! [A]', 'B', 'Loading...']);
expect(ReactNoop.getChildren()).toEqual([]);
@@ -463,17 +486,16 @@ describe('ReactSuspenseWithNoopRenderer', () => {
});
it('switches to an inner fallback even if it expires later', async () => {
+ // Advance the virtual time so that we're closer to the edge of a bucket.
+ ReactNoop.expire(200);
+
ReactNoop.render(
- }>
-
- }>
-
+ }>
+
+ }>
+
,
@@ -492,8 +514,8 @@ describe('ReactSuspenseWithNoopRenderer', () => {
// Expire the outer timeout, but don't expire the inner one.
// We should see the outer loading placeholder.
- ReactNoop.expire(1500);
- await advanceTimers(1500);
+ ReactNoop.expire(250);
+ await advanceTimers(250);
expect(Scheduler).toFlushWithoutYielding();
expect(ReactNoop.getChildren()).toEqual([
span('Sync'),
@@ -501,11 +523,11 @@ describe('ReactSuspenseWithNoopRenderer', () => {
]);
// Resolve the outer promise.
- ReactNoop.expire(2000);
- await advanceTimers(2000);
- // At this point, 3.5 seconds have elapsed total. The outer placeholder
- // timed out at 1.5 seconds. So, 2 seconds have elapsed since the
- // placeholder timed out. That means we still haven't reached the 2.5 second
+ ReactNoop.expire(50);
+ await advanceTimers(50);
+ // At this point, 250ms have elapsed total. The outer placeholder
+ // timed out at around 150-200ms. So, 50-100ms have elapsed since the
+ // placeholder timed out. That means we still haven't reached the 150ms
// threshold of the inner placeholder.
expect(Scheduler).toHaveYielded(['Promise resolved [Outer content]']);
expect(Scheduler).toFlushAndYield([
@@ -522,7 +544,7 @@ describe('ReactSuspenseWithNoopRenderer', () => {
// Expire the inner timeout.
ReactNoop.expire(500);
await advanceTimers(500);
- // Now that 2.5 seconds have elapsed since the outer placeholder timed out,
+ // Now that 750ms have elapsed since the outer placeholder timed out,
// we can timeout the inner placeholder.
expect(ReactNoop.getChildren()).toEqual([
span('Sync'),
@@ -595,10 +617,10 @@ describe('ReactSuspenseWithNoopRenderer', () => {
expect(ReactNoop.getChildren()).toEqual([span('Loading (outer)...')]);
});
- it('expires early with a `maxDuration` option', async () => {
+ it('expires early by default', async () => {
ReactNoop.render(
- }>
+ }>
@@ -632,7 +654,7 @@ describe('ReactSuspenseWithNoopRenderer', () => {
it('resolves successfully even if fallback render is pending', async () => {
ReactNoop.render(
- }>
+ }>
,
);
@@ -656,7 +678,7 @@ describe('ReactSuspenseWithNoopRenderer', () => {
it('a Suspense component correctly handles more than one suspended child', async () => {
ReactNoop.render(
- }>
+ }>
,
@@ -681,7 +703,7 @@ describe('ReactSuspenseWithNoopRenderer', () => {
it('can resume rendering earlier than a timeout', async () => {
ReactNoop.render(
- }>
+ }>
,
);
@@ -704,10 +726,11 @@ describe('ReactSuspenseWithNoopRenderer', () => {
expect(ReactNoop.getChildren()).toEqual([span('Async')]);
});
- it('starts working on an update even if its priority falls between two suspended levels', async () => {
+ // TODO: This cannot be tested until we have a way to long-suspend navigations.
+ it.skip('starts working on an update even if its priority falls between two suspended levels', async () => {
function App(props) {
return (
- } maxDuration={10000}>
+ }>
{props.text === 'C' ? (
) : (
@@ -802,55 +825,11 @@ describe('ReactSuspenseWithNoopRenderer', () => {
expect(ReactNoop.getChildren()).toEqual([span('goodbye')]);
});
- describe('a Delay component', () => {
- function Never() {
- // Throws a promise that resolves after some arbitrarily large
- // number of seconds. The idea is that this component will never
- // resolve. It's always wrapped by a Suspense.
- throw new Promise(resolve => setTimeout(() => resolve(), 10000));
- }
-
- function Delay({ms}) {
- // Once ms has elapsed, render null. This allows the rest of the
- // tree to resume rendering.
- return (
-
-
-
- );
- }
-
- function DebouncedText({text, ms}) {
- return (
-
-
-
-
- );
- }
-
- it('works', async () => {
- ReactNoop.render();
- expect(Scheduler).toFlushAndYield(['A']);
- expect(ReactNoop.getChildren()).toEqual([]);
-
- await advanceTimers(800);
- ReactNoop.expire(800);
- expect(Scheduler).toFlushWithoutYielding();
- expect(ReactNoop.getChildren()).toEqual([]);
-
- await advanceTimers(1000);
- ReactNoop.expire(1000);
- expect(Scheduler).toFlushWithoutYielding();
- expect(ReactNoop.getChildren()).toEqual([span('A')]);
- });
- });
-
describe('sync mode', () => {
it('times out immediately', async () => {
function App() {
return (
- }>
+ }>
);
@@ -889,7 +868,7 @@ describe('ReactSuspenseWithNoopRenderer', () => {
const text = React.createRef(null);
function App() {
return (
- }>
+ }>
@@ -969,9 +948,7 @@ describe('ReactSuspenseWithNoopRenderer', () => {
function App() {
return (
- }>
+ }>
{text => (
@@ -1104,9 +1081,7 @@ describe('ReactSuspenseWithNoopRenderer', () => {
function App() {
return (
- }>
+ }>
{text => (
@@ -1250,9 +1225,7 @@ describe('ReactSuspenseWithNoopRenderer', () => {
function App() {
return (
- }>
+ }>
@@ -1517,9 +1490,7 @@ describe('ReactSuspenseWithNoopRenderer', () => {
function App() {
return (
- }>
+ }>
diff --git a/packages/react/src/__tests__/ReactProfiler-test.internal.js b/packages/react/src/__tests__/ReactProfiler-test.internal.js
index ed4cca9b566e1..8df35e979e8fd 100644
--- a/packages/react/src/__tests__/ReactProfiler-test.internal.js
+++ b/packages/react/src/__tests__/ReactProfiler-test.internal.js
@@ -2362,10 +2362,8 @@ describe('Profiler', () => {
() => {
ReactTestRenderer.create(
- }>
-
+ }>
+
,
);
@@ -2416,10 +2414,8 @@ describe('Profiler', () => {
() => {
ReactTestRenderer.create(
- }>
-
+ }>
+
,
);
@@ -2455,10 +2451,8 @@ describe('Profiler', () => {
() => {
ReactTestRenderer.create(
- }>
-
+ }>
+
,
{
@@ -2468,8 +2462,8 @@ describe('Profiler', () => {
},
);
- Scheduler.advanceTime(1500);
- await awaitableAdvanceTimers(1500);
+ Scheduler.advanceTime(400);
+ await awaitableAdvanceTimers(400);
expect(Scheduler).toFlushAndYield([
'Suspend [loaded]',
@@ -2477,8 +2471,8 @@ describe('Profiler', () => {
]);
expect(onInteractionScheduledWorkCompleted).not.toHaveBeenCalled();
- Scheduler.advanceTime(2500);
- await awaitableAdvanceTimers(2500);
+ Scheduler.advanceTime(500);
+ await awaitableAdvanceTimers(500);
expect(Scheduler).toHaveYielded(['Promise resolved [loaded]']);
expect(Scheduler).toFlushAndYield(['AsyncText [loaded]']);
@@ -2502,10 +2496,8 @@ describe('Profiler', () => {
() => {
ReactTestRenderer.create(
- }>
-
+ }>
+
,
{unstable_isConcurrent: true},
@@ -2519,7 +2511,7 @@ describe('Profiler', () => {
expect(onInteractionScheduledWorkCompleted).not.toHaveBeenCalled();
- jest.advanceTimersByTime(1000);
+ jest.advanceTimersByTime(100);
await resourcePromise;
expect(Scheduler).toHaveYielded(['Promise resolved [loaded]']);
expect(Scheduler).toFlushAndYield(['AsyncText [loaded]']);
@@ -2545,10 +2537,8 @@ describe('Profiler', () => {
() => {
renderer = ReactTestRenderer.create(
- }>
-
+ }>
+
,
@@ -2579,10 +2569,8 @@ describe('Profiler', () => {
() => {
renderer.update(
- }>
-
+ }>
+
,
@@ -2600,8 +2588,8 @@ describe('Profiler', () => {
expect(onInteractionScheduledWorkCompleted).not.toHaveBeenCalled();
- Scheduler.advanceTime(1000);
- jest.advanceTimersByTime(1000);
+ Scheduler.advanceTime(100);
+ jest.advanceTimersByTime(100);
await originalPromise;
expect(renderer.toJSON()).toEqual(['loaded', 'updated']);
@@ -2631,10 +2619,8 @@ describe('Profiler', () => {
() => {
renderer = ReactTestRenderer.create(
- }>
-
+ }>
+
,
@@ -2651,8 +2637,8 @@ describe('Profiler', () => {
expect(onInteractionScheduledWorkCompleted).not.toHaveBeenCalled();
expect(onRender).not.toHaveBeenCalled();
- Scheduler.advanceTime(500);
- jest.advanceTimersByTime(500);
+ Scheduler.advanceTime(50);
+ jest.advanceTimersByTime(50);
const highPriUpdateInteraction = {
id: 1,
@@ -2669,10 +2655,8 @@ describe('Profiler', () => {
() => {
renderer.update(
- }>
-
+ }>
+
,
@@ -2695,8 +2679,8 @@ describe('Profiler', () => {
expect(onInteractionScheduledWorkCompleted).toHaveBeenCalledTimes(0);
- Scheduler.advanceTime(500);
- jest.advanceTimersByTime(500);
+ Scheduler.advanceTime(50);
+ jest.advanceTimersByTime(50);
await originalPromise;
expect(Scheduler).toHaveYielded(['Promise resolved [loaded]']);
expect(Scheduler).toFlushAndYield(['AsyncText [loaded]']);
diff --git a/packages/react/src/__tests__/ReactProfilerDOM-test.internal.js b/packages/react/src/__tests__/ReactProfilerDOM-test.internal.js
index 21b691c4ff9f6..8d2bf3cf8b337 100644
--- a/packages/react/src/__tests__/ReactProfilerDOM-test.internal.js
+++ b/packages/react/src/__tests__/ReactProfilerDOM-test.internal.js
@@ -88,7 +88,7 @@ describe('ProfilerDOM', () => {
const root = ReactDOM.unstable_createRoot(element);
batch = root.createBatch();
batch.render(
- }>
+ }>
,
);