diff --git a/packages/react-reconciler/src/ReactFiberWorkLoop.js b/packages/react-reconciler/src/ReactFiberWorkLoop.js index 76698b67185..edca5c6458c 100644 --- a/packages/react-reconciler/src/ReactFiberWorkLoop.js +++ b/packages/react-reconciler/src/ReactFiberWorkLoop.js @@ -27,6 +27,7 @@ import { revertPassiveEffectsChange, warnAboutUnmockedScheduler, flushSuspenseFallbacksInTests, + disableSchedulerTimeoutBasedOnReactExpirationTime, } from 'shared/ReactFeatureFlags'; import ReactSharedInternals from 'shared/ReactSharedInternals'; import invariant from 'shared/invariant'; @@ -530,7 +531,10 @@ function scheduleCallbackForRoot( ); } else { let options = null; - if (expirationTime !== Never) { + if ( + !disableSchedulerTimeoutBasedOnReactExpirationTime && + expirationTime !== Never + ) { let timeout = expirationTimeToMs(expirationTime) - now(); options = {timeout}; } diff --git a/packages/react-reconciler/src/__tests__/ReactDisableSchedulerTimeoutBasedOnReactExpirationTime-test.internal.js b/packages/react-reconciler/src/__tests__/ReactDisableSchedulerTimeoutBasedOnReactExpirationTime-test.internal.js new file mode 100644 index 00000000000..83603d625fb --- /dev/null +++ b/packages/react-reconciler/src/__tests__/ReactDisableSchedulerTimeoutBasedOnReactExpirationTime-test.internal.js @@ -0,0 +1,96 @@ +let React; +let ReactFeatureFlags; +let ReactNoop; +let Scheduler; +let Suspense; +let scheduleCallback; +let NormalPriority; + +describe('ReactSuspenseList', () => { + beforeEach(() => { + jest.resetModules(); + ReactFeatureFlags = require('shared/ReactFeatureFlags'); + ReactFeatureFlags.debugRenderPhaseSideEffectsForStrictMode = false; + ReactFeatureFlags.replayFailedUnitOfWorkWithInvokeGuardedCallback = false; + ReactFeatureFlags.disableSchedulerTimeoutBasedOnReactExpirationTime = true; + React = require('react'); + ReactNoop = require('react-noop-renderer'); + Scheduler = require('scheduler'); + Suspense = React.Suspense; + + scheduleCallback = Scheduler.unstable_scheduleCallback; + NormalPriority = Scheduler.unstable_NormalPriority; + }); + + function Text(props) { + Scheduler.unstable_yieldValue(props.text); + return props.text; + } + + function createAsyncText(text) { + let resolved = false; + let Component = function() { + if (!resolved) { + Scheduler.unstable_yieldValue('Suspend! [' + text + ']'); + throw promise; + } + return ; + }; + let promise = new Promise(resolve => { + Component.resolve = function() { + resolved = true; + return resolve(); + }; + }); + return Component; + } + + it('appends rendering tasks to the end of the priority queue', async () => { + const A = createAsyncText('A'); + const B = createAsyncText('B'); + + function App({show}) { + return ( + }> + {show ? : null} + {show ? : null} + + ); + } + + const root = ReactNoop.createRoot(null); + + root.render(); + expect(Scheduler).toFlushAndYield([]); + + root.render(); + expect(Scheduler).toFlushAndYield([ + 'Suspend! [A]', + 'Suspend! [B]', + 'Loading...', + ]); + expect(root).toMatchRenderedOutput(null); + + Scheduler.unstable_advanceTime(2000); + expect(root).toMatchRenderedOutput(null); + + scheduleCallback(NormalPriority, () => { + Scheduler.unstable_yieldValue('Resolve A'); + A.resolve(); + }); + scheduleCallback(NormalPriority, () => { + Scheduler.unstable_yieldValue('Resolve B'); + B.resolve(); + }); + + // This resolves A and schedules a task for React to retry. + await expect(Scheduler).toFlushAndYieldThrough(['Resolve A']); + + // The next task that flushes should be the one that resolves B. The render + // task should not jump the queue ahead of B. + await expect(Scheduler).toFlushAndYieldThrough(['Resolve B']); + + expect(Scheduler).toFlushAndYield(['A', 'B']); + expect(root).toMatchRenderedOutput('AB'); + }); +}); diff --git a/packages/shared/ReactFeatureFlags.js b/packages/shared/ReactFeatureFlags.js index da01edb1387..842147b03af 100644 --- a/packages/shared/ReactFeatureFlags.js +++ b/packages/shared/ReactFeatureFlags.js @@ -94,3 +94,5 @@ export const enableSuspenseCallback = false; export const warnAboutDefaultPropsOnFunctionComponents = false; export const disableLegacyContext = false; + +export const disableSchedulerTimeoutBasedOnReactExpirationTime = false; diff --git a/packages/shared/forks/ReactFeatureFlags.native-fb.js b/packages/shared/forks/ReactFeatureFlags.native-fb.js index 6b95b92d207..894d57f02e6 100644 --- a/packages/shared/forks/ReactFeatureFlags.native-fb.js +++ b/packages/shared/forks/ReactFeatureFlags.native-fb.js @@ -41,6 +41,7 @@ export const enableUserBlockingEvents = false; export const enableSuspenseCallback = false; export const warnAboutDefaultPropsOnFunctionComponents = false; export const disableLegacyContext = false; +export const disableSchedulerTimeoutBasedOnReactExpirationTime = false; // Only used in www builds. export function addUserTimingListener() { diff --git a/packages/shared/forks/ReactFeatureFlags.native-oss.js b/packages/shared/forks/ReactFeatureFlags.native-oss.js index 08525d0c093..387bd8656a8 100644 --- a/packages/shared/forks/ReactFeatureFlags.native-oss.js +++ b/packages/shared/forks/ReactFeatureFlags.native-oss.js @@ -36,6 +36,7 @@ export const enableUserBlockingEvents = false; export const enableSuspenseCallback = false; export const warnAboutDefaultPropsOnFunctionComponents = false; export const disableLegacyContext = false; +export const disableSchedulerTimeoutBasedOnReactExpirationTime = false; // Only used in www builds. export function addUserTimingListener() { diff --git a/packages/shared/forks/ReactFeatureFlags.persistent.js b/packages/shared/forks/ReactFeatureFlags.persistent.js index 478586ff80a..8aec846c197 100644 --- a/packages/shared/forks/ReactFeatureFlags.persistent.js +++ b/packages/shared/forks/ReactFeatureFlags.persistent.js @@ -36,6 +36,7 @@ export const enableUserBlockingEvents = false; export const enableSuspenseCallback = false; export const warnAboutDefaultPropsOnFunctionComponents = false; export const disableLegacyContext = false; +export const disableSchedulerTimeoutBasedOnReactExpirationTime = false; // Only used in www builds. export function addUserTimingListener() { diff --git a/packages/shared/forks/ReactFeatureFlags.test-renderer.js b/packages/shared/forks/ReactFeatureFlags.test-renderer.js index 91bfab22efa..b1f941bec09 100644 --- a/packages/shared/forks/ReactFeatureFlags.test-renderer.js +++ b/packages/shared/forks/ReactFeatureFlags.test-renderer.js @@ -36,6 +36,7 @@ export const enableUserBlockingEvents = false; export const enableSuspenseCallback = false; export const warnAboutDefaultPropsOnFunctionComponents = false; export const disableLegacyContext = false; +export const disableSchedulerTimeoutBasedOnReactExpirationTime = false; // Only used in www builds. export function addUserTimingListener() { diff --git a/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js b/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js index 28f7d534292..0a9df80e9a8 100644 --- a/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js +++ b/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js @@ -36,6 +36,7 @@ export const enableUserBlockingEvents = false; export const enableSuspenseCallback = true; export const warnAboutDefaultPropsOnFunctionComponents = false; export const disableLegacyContext = false; +export const disableSchedulerTimeoutBasedOnReactExpirationTime = false; // Only used in www builds. export function addUserTimingListener() { diff --git a/packages/shared/forks/ReactFeatureFlags.www.js b/packages/shared/forks/ReactFeatureFlags.www.js index 3f2c4c6b519..4999747c44e 100644 --- a/packages/shared/forks/ReactFeatureFlags.www.js +++ b/packages/shared/forks/ReactFeatureFlags.www.js @@ -22,6 +22,7 @@ export const { revertPassiveEffectsChange, enableUserBlockingEvents, disableLegacyContext, + disableSchedulerTimeoutBasedOnReactExpirationTime, } = require('ReactFeatureFlags'); // In www, we have experimental support for gathering data