diff --git a/packages/react-reconciler/src/ReactFiberWorkLoop.js b/packages/react-reconciler/src/ReactFiberWorkLoop.js
index ad6ff4037edf1..8bf99ce1139bf 100644
--- a/packages/react-reconciler/src/ReactFiberWorkLoop.js
+++ b/packages/react-reconciler/src/ReactFiberWorkLoop.js
@@ -25,6 +25,7 @@ import {
warnAboutUnmockedScheduler,
flushSuspenseFallbacksInTests,
disableSchedulerTimeoutBasedOnReactExpirationTime,
+ enableTrainModelFix,
} from 'shared/ReactFeatureFlags';
import ReactSharedInternals from 'shared/ReactSharedInternals';
import invariant from 'shared/invariant';
@@ -539,9 +540,19 @@ function getNextRootExpirationTimeToWorkOn(root: FiberRoot): ExpirationTime {
// on whichever is higher priority.
const lastPingedTime = root.lastPingedTime;
const nextKnownPendingLevel = root.nextKnownPendingLevel;
- return lastPingedTime > nextKnownPendingLevel
- ? lastPingedTime
- : nextKnownPendingLevel;
+ const nextLevel =
+ lastPingedTime > nextKnownPendingLevel
+ ? lastPingedTime
+ : nextKnownPendingLevel;
+ if (
+ enableTrainModelFix &&
+ nextLevel <= Idle &&
+ firstPendingTime !== nextLevel
+ ) {
+ // Don't work on Idle/Never priority unless everything else is committed.
+ return NoWork;
+ }
+ return nextLevel;
}
// Use this function to schedule a task for a root. There's only one task per
@@ -2362,7 +2373,7 @@ export function pingSuspendedRoot(
// Mark the time at which this ping was scheduled.
root.lastPingedTime = suspendedTime;
- if (root.finishedExpirationTime === suspendedTime) {
+ if (!enableTrainModelFix && root.finishedExpirationTime === suspendedTime) {
// If there's a pending fallback waiting to commit, throw it away.
root.finishedExpirationTime = NoWork;
root.finishedWork = null;
diff --git a/packages/react-reconciler/src/__tests__/ReactSuspenseWithNoopRenderer-test.internal.js b/packages/react-reconciler/src/__tests__/ReactSuspenseWithNoopRenderer-test.internal.js
index 86fb78244188d..97f247895d553 100644
--- a/packages/react-reconciler/src/__tests__/ReactSuspenseWithNoopRenderer-test.internal.js
+++ b/packages/react-reconciler/src/__tests__/ReactSuspenseWithNoopRenderer-test.internal.js
@@ -2222,7 +2222,8 @@ describe('ReactSuspenseWithNoopRenderer', () => {
Scheduler.unstable_runWithPriority(Scheduler.unstable_IdlePriority, () =>
ReactNoop.render(),
);
- expect(Scheduler).toFlushAndYield(['Suspend! [A]', 'Loading A...']);
+ // We won't even work on Idle priority.
+ expect(Scheduler).toFlushAndYield([]);
// We're still suspended.
expect(ReactNoop.getChildren()).toEqual([]);
@@ -2789,4 +2790,116 @@ describe('ReactSuspenseWithNoopRenderer', () => {
expect(root).toMatchRenderedOutput();
});
+
+ it('should not render hidden content while suspended on higher pri', async () => {
+ function Offscreen() {
+ Scheduler.unstable_yieldValue('Offscreen');
+ return 'Offscreen';
+ }
+ function App({showContent}) {
+ React.useLayoutEffect(() => {
+ Scheduler.unstable_yieldValue('Commit');
+ });
+ return (
+ <>
+
+
+
+ }>
+ {showContent ? : null}
+
+ >
+ );
+ }
+
+ // Initial render.
+ ReactNoop.render();
+ expect(Scheduler).toFlushAndYieldThrough(['Commit']);
+ expect(ReactNoop).toMatchRenderedOutput();
+
+ // Start transition.
+ React.unstable_withSuspenseConfig(
+ () => {
+ ReactNoop.render();
+ },
+ {timeoutMs: 2000},
+ );
+
+ expect(Scheduler).toFlushAndYield(['Suspend! [A]', 'Loading...']);
+ Scheduler.unstable_advanceTime(2000);
+ await advanceTimers(2000);
+ expect(Scheduler).toHaveYielded(['Promise resolved [A]']);
+ expect(Scheduler).toFlushAndYieldThrough(['A', 'Commit']);
+ expect(ReactNoop).toMatchRenderedOutput(
+ <>
+
+
+ >,
+ );
+ expect(Scheduler).toFlushAndYield(['Offscreen']);
+ expect(ReactNoop).toMatchRenderedOutput(
+ <>
+ Offscreen
+
+ >,
+ );
+ });
+
+ it('should be able to unblock higher pri content before suspended hidden', async () => {
+ function Offscreen() {
+ Scheduler.unstable_yieldValue('Offscreen');
+ return 'Offscreen';
+ }
+ function App({showContent}) {
+ React.useLayoutEffect(() => {
+ Scheduler.unstable_yieldValue('Commit');
+ });
+ return (
+ }>
+
+ {showContent ? : null}
+
+ );
+ }
+
+ // Initial render.
+ ReactNoop.render();
+ expect(Scheduler).toFlushAndYieldThrough(['Commit']);
+ expect(ReactNoop).toMatchRenderedOutput();
+
+ // Partially render through the hidden content.
+ expect(Scheduler).toFlushAndYieldThrough(['Suspend! [A]']);
+
+ // Start transition.
+ React.unstable_withSuspenseConfig(
+ () => {
+ ReactNoop.render();
+ },
+ {timeoutMs: 5000},
+ );
+
+ expect(Scheduler).toFlushAndYield(['Suspend! [A]', 'Loading...']);
+ Scheduler.unstable_advanceTime(2000);
+ await advanceTimers(2000);
+ expect(Scheduler).toHaveYielded(['Promise resolved [A]']);
+ expect(Scheduler).toFlushAndYieldThrough(['A', 'Commit']);
+ expect(ReactNoop).toMatchRenderedOutput(
+ <>
+
+
+ >,
+ );
+ expect(Scheduler).toFlushAndYield(['A', 'Offscreen']);
+ expect(ReactNoop).toMatchRenderedOutput(
+ <>
+
+ Offscreen
+
+
+ >,
+ );
+ });
});
diff --git a/packages/shared/ReactFeatureFlags.js b/packages/shared/ReactFeatureFlags.js
index fc30d6f9a3549..67f1f93669122 100644
--- a/packages/shared/ReactFeatureFlags.js
+++ b/packages/shared/ReactFeatureFlags.js
@@ -88,6 +88,8 @@ export const disableLegacyContext = false;
export const disableSchedulerTimeoutBasedOnReactExpirationTime = false;
+export const enableTrainModelFix = __EXPERIMENTAL__;
+
export const enableTrustedTypesIntegration = false;
// Flag to turn event.target and event.currentTarget in ReactNative from a reactTag to a component instance
diff --git a/packages/shared/forks/ReactFeatureFlags.native-fb.js b/packages/shared/forks/ReactFeatureFlags.native-fb.js
index 4eb32dbaaf423..05defbb34feb4 100644
--- a/packages/shared/forks/ReactFeatureFlags.native-fb.js
+++ b/packages/shared/forks/ReactFeatureFlags.native-fb.js
@@ -42,6 +42,7 @@ export const warnAboutDefaultPropsOnFunctionComponents = false;
export const warnAboutStringRefs = false;
export const disableLegacyContext = false;
export const disableSchedulerTimeoutBasedOnReactExpirationTime = false;
+export const enableTrainModelFix = false;
export const enableTrustedTypesIntegration = false;
// Only used in www builds.
diff --git a/packages/shared/forks/ReactFeatureFlags.native-oss.js b/packages/shared/forks/ReactFeatureFlags.native-oss.js
index 1006296f6502c..324f9f2b1273c 100644
--- a/packages/shared/forks/ReactFeatureFlags.native-oss.js
+++ b/packages/shared/forks/ReactFeatureFlags.native-oss.js
@@ -36,6 +36,7 @@ export const warnAboutDefaultPropsOnFunctionComponents = false;
export const warnAboutStringRefs = false;
export const disableLegacyContext = false;
export const disableSchedulerTimeoutBasedOnReactExpirationTime = false;
+export const enableTrainModelFix = false;
export const enableTrustedTypesIntegration = false;
export const enableNativeTargetAsInstance = false;
diff --git a/packages/shared/forks/ReactFeatureFlags.persistent.js b/packages/shared/forks/ReactFeatureFlags.persistent.js
index ab0a07ee9d738..73efda9e02be0 100644
--- a/packages/shared/forks/ReactFeatureFlags.persistent.js
+++ b/packages/shared/forks/ReactFeatureFlags.persistent.js
@@ -36,6 +36,7 @@ export const warnAboutDefaultPropsOnFunctionComponents = false;
export const warnAboutStringRefs = false;
export const disableLegacyContext = false;
export const disableSchedulerTimeoutBasedOnReactExpirationTime = false;
+export const enableTrainModelFix = false;
export const enableTrustedTypesIntegration = false;
export const enableNativeTargetAsInstance = false;
diff --git a/packages/shared/forks/ReactFeatureFlags.test-renderer.js b/packages/shared/forks/ReactFeatureFlags.test-renderer.js
index 1d5049ffffad4..2fa3be510cf81 100644
--- a/packages/shared/forks/ReactFeatureFlags.test-renderer.js
+++ b/packages/shared/forks/ReactFeatureFlags.test-renderer.js
@@ -36,6 +36,7 @@ export const warnAboutDefaultPropsOnFunctionComponents = false;
export const warnAboutStringRefs = false;
export const disableLegacyContext = false;
export const disableSchedulerTimeoutBasedOnReactExpirationTime = false;
+export const enableTrainModelFix = false;
export const enableTrustedTypesIntegration = false;
export const enableNativeTargetAsInstance = false;
diff --git a/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js b/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js
index 20d16e23abadc..32234022711ef 100644
--- a/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js
+++ b/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js
@@ -34,6 +34,7 @@ export const warnAboutDefaultPropsOnFunctionComponents = false;
export const warnAboutStringRefs = false;
export const disableLegacyContext = false;
export const disableSchedulerTimeoutBasedOnReactExpirationTime = false;
+export const enableTrainModelFix = false;
export const enableTrustedTypesIntegration = false;
export const enableNativeTargetAsInstance = false;
diff --git a/packages/shared/forks/ReactFeatureFlags.www.js b/packages/shared/forks/ReactFeatureFlags.www.js
index ba025eefd3fb6..72e4ae4547bb3 100644
--- a/packages/shared/forks/ReactFeatureFlags.www.js
+++ b/packages/shared/forks/ReactFeatureFlags.www.js
@@ -16,6 +16,7 @@ export const {
disableInputAttributeSyncing,
enableTrustedTypesIntegration,
enableSelectiveHydration,
+ enableTrainModelFix,
} = require('ReactFeatureFlags');
// In www, we have experimental support for gathering data