diff --git a/packages/react-dom/src/__tests__/ReactDOMFiberAsync-test.internal.js b/packages/react-dom/src/__tests__/ReactDOMFiberAsync-test.internal.js index 68e9ae3e722f7..9ed2c641cdd1f 100644 --- a/packages/react-dom/src/__tests__/ReactDOMFiberAsync-test.internal.js +++ b/packages/react-dom/src/__tests__/ReactDOMFiberAsync-test.internal.js @@ -720,16 +720,14 @@ describe('ReactDOMFiberAsync', () => { let root = ReactDOM.unstable_createRoot(container); root.render( - - Initial - , + Initial, ); Scheduler.flushAll(); expect(container.textContent).toBe('Initial'); root.render( - + , ); diff --git a/packages/react-dom/src/__tests__/ReactDOMServerPartialHydration-test.internal.js b/packages/react-dom/src/__tests__/ReactDOMServerPartialHydration-test.internal.js index b0e62e2fc789c..36f444139750a 100644 --- a/packages/react-dom/src/__tests__/ReactDOMServerPartialHydration-test.internal.js +++ b/packages/react-dom/src/__tests__/ReactDOMServerPartialHydration-test.internal.js @@ -363,7 +363,7 @@ describe('ReactDOMServerPartialHydration', () => {
- + @@ -703,7 +703,7 @@ describe('ReactDOMServerPartialHydration', () => { expect(ref.current).toBe(span); }); - it('replaces the fallback within the maxDuration if there is a nested suspense', async () => { + it('replaces the fallback within the suspended time if there is a nested suspense', async () => { let suspend = false; let promise = new Promise(resolvePromise => {}); let ref = React.createRef(); @@ -724,7 +724,7 @@ describe('ReactDOMServerPartialHydration', () => { function App() { return (
- + @@ -751,7 +751,7 @@ describe('ReactDOMServerPartialHydration', () => { let root = ReactDOM.unstable_createRoot(container, {hydrate: true}); root.render(); Scheduler.flushAll(); - // This will have exceeded the maxDuration so we should timeout. + // This will have exceeded the suspended time so we should timeout. jest.advanceTimersByTime(500); // The boundary should longer be suspended for the middle content // even though the inner boundary is still suspended. @@ -762,7 +762,7 @@ describe('ReactDOMServerPartialHydration', () => { expect(ref.current).toBe(span); }); - it('replaces the fallback within the maxDuration if there is a nested suspense in a nested suspense', async () => { + it('replaces the fallback within the suspended time if there is a nested suspense in a nested suspense', async () => { let suspend = false; let promise = new Promise(resolvePromise => {}); let ref = React.createRef(); @@ -784,7 +784,7 @@ describe('ReactDOMServerPartialHydration', () => { return (
- + @@ -812,7 +812,7 @@ describe('ReactDOMServerPartialHydration', () => { let root = ReactDOM.unstable_createRoot(container, {hydrate: true}); root.render(); Scheduler.flushAll(); - // This will have exceeded the maxDuration so we should timeout. + // This will have exceeded the suspended time so we should timeout. jest.advanceTimersByTime(500); // The boundary should longer be suspended for the middle content // even though the inner boundary is still suspended. diff --git a/packages/react-dom/src/__tests__/ReactDOMSuspensePlaceholder-test.js b/packages/react-dom/src/__tests__/ReactDOMSuspensePlaceholder-test.js index 14e02d396da7a..7a5df7629e088 100644 --- a/packages/react-dom/src/__tests__/ReactDOMSuspensePlaceholder-test.js +++ b/packages/react-dom/src/__tests__/ReactDOMSuspensePlaceholder-test.js @@ -74,12 +74,12 @@ describe('ReactDOMSuspensePlaceholder', () => { ]; function App() { return ( - }> + }>
- +
@@ -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 e5ac3a605aeef..e6b04911a7025 100644 --- a/packages/react-reconciler/src/ReactFiberBeginWork.js +++ b/packages/react-reconciler/src/ReactFiberBeginWork.js @@ -157,6 +157,7 @@ let didWarnAboutContextTypeOnFunctionComponent; let didWarnAboutGetDerivedStateOnFunctionComponent; let didWarnAboutFunctionRefs; export let didWarnAboutReassigningProps; +let didWarnAboutMaxDuration; if (__DEV__) { didWarnAboutBadClass = {}; @@ -165,6 +166,7 @@ if (__DEV__) { didWarnAboutGetDerivedStateOnFunctionComponent = {}; didWarnAboutFunctionRefs = {}; didWarnAboutReassigningProps = false; + didWarnAboutMaxDuration = false; } export function reconcileChildren( @@ -1409,6 +1411,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 006cc5e749029..f3b4d749df80b 100644 --- a/packages/react-reconciler/src/ReactFiberUnwindWork.js +++ b/packages/react-reconciler/src/ReactFiberUnwindWork.js @@ -232,16 +232,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( - }> + }> , );