diff --git a/packages/react-reconciler/src/ReactFiberScheduler.new.js b/packages/react-reconciler/src/ReactFiberScheduler.new.js
index e2b4517de23fe..c86bd58ce6a57 100644
--- a/packages/react-reconciler/src/ReactFiberScheduler.new.js
+++ b/packages/react-reconciler/src/ReactFiberScheduler.new.js
@@ -742,26 +742,38 @@ function renderRoot(
}
startWorkLoopTimer(workInProgress);
+
+ // TODO: Fork renderRoot into renderRootSync and renderRootAsync
+ if (isSync) {
+ if (expirationTime !== Sync) {
+ // An async update expired. There may be other expired updates on
+ // this root. We should render all the expired work in a
+ // single batch.
+ const currentTime = requestCurrentTime();
+ if (currentTime < expirationTime) {
+ // Restart at the current time.
+ workPhase = prevWorkPhase;
+ resetContextDependencies();
+ ReactCurrentDispatcher.current = prevDispatcher;
+ if (enableSchedulerTracing) {
+ __interactionsRef.current = ((prevInteractions: any): Set<
+ Interaction,
+ >);
+ }
+ return renderRoot.bind(null, root, currentTime);
+ }
+ }
+ } else {
+ // Since we know we're in a React event, we can clear the current
+ // event time. The next update will compute a new event time.
+ currentEventTime = NoWork;
+ }
+
do {
try {
if (isSync) {
- if (expirationTime !== Sync) {
- // An async update expired. There may be other expired updates on
- // this root. We should render all the expired work in a
- // single batch.
- const currentTime = requestCurrentTime();
- if (currentTime < expirationTime) {
- // Restart at the current time.
- workPhase = prevWorkPhase;
- ReactCurrentDispatcher.current = prevDispatcher;
- return renderRoot.bind(null, root, currentTime);
- }
- }
workLoopSync();
} else {
- // Since we know we're in a React event, we can clear the current
- // event time. The next update will compute a new event time.
- currentEventTime = NoWork;
workLoop();
}
break;
diff --git a/packages/react-reconciler/src/__tests__/ReactSuspenseWithNoopRenderer-test.internal.js b/packages/react-reconciler/src/__tests__/ReactSuspenseWithNoopRenderer-test.internal.js
index b68b239f80e68..89f3f2a698c62 100644
--- a/packages/react-reconciler/src/__tests__/ReactSuspenseWithNoopRenderer-test.internal.js
+++ b/packages/react-reconciler/src/__tests__/ReactSuspenseWithNoopRenderer-test.internal.js
@@ -827,6 +827,50 @@ describe('ReactSuspenseWithNoopRenderer', () => {
expect(ReactNoop.getChildren()).toEqual([span('goodbye')]);
});
+ it('a suspended update that expires', async () => {
+ // Regression test. This test used to fall into an infinite loop.
+ function ExpensiveText({text}) {
+ // This causes the update to expire.
+ Scheduler.advanceTime(10000);
+ // Then something suspends.
+ return ;
+ }
+
+ function App() {
+ return (
+
+
+
+
+
+ );
+ }
+
+ ReactNoop.render();
+ expect(Scheduler).toFlushAndYield([
+ 'Suspend! [A]',
+ 'Suspend! [B]',
+ 'Suspend! [C]',
+ ]);
+ expect(ReactNoop).toMatchRenderedOutput('Loading...');
+
+ await advanceTimers(200000);
+ expect(Scheduler).toHaveYielded([
+ 'Promise resolved [A]',
+ 'Promise resolved [B]',
+ 'Promise resolved [C]',
+ ]);
+
+ expect(Scheduler).toFlushAndYield(['A', 'B', 'C']);
+ expect(ReactNoop).toMatchRenderedOutput(
+
+
+
+
+ ,
+ );
+ });
+
describe('sync mode', () => {
it('times out immediately', async () => {
function App() {