diff --git a/packages/react-dom/src/__tests__/ReactUpdates-test.js b/packages/react-dom/src/__tests__/ReactUpdates-test.js
index ecf5c3467108..b29af8f52fe6 100644
--- a/packages/react-dom/src/__tests__/ReactUpdates-test.js
+++ b/packages/react-dom/src/__tests__/ReactUpdates-test.js
@@ -1364,4 +1364,37 @@ describe('ReactUpdates', () => {
ReactDOM.render(, container);
}).toThrow('Maximum');
});
+
+ it('can schedule ridiculously many updates within the same batch without triggering a maximum update error', () => {
+ const subscribers = [];
+
+ class Child extends React.Component {
+ state = {value: 'initial'};
+ componentDidMount() {
+ subscribers.push(this);
+ }
+ render() {
+ return null;
+ }
+ }
+
+ class App extends React.Component {
+ render() {
+ const children = [];
+ for (let i = 0; i < 1200; i++) {
+ children.push();
+ }
+ return children;
+ }
+ }
+
+ const container = document.createElement('div');
+ ReactDOM.render(, container);
+
+ ReactDOM.unstable_batchedUpdates(() => {
+ subscribers.forEach(s => {
+ s.setState({value: 'update'});
+ });
+ });
+ });
});
diff --git a/packages/react-reconciler/src/ReactFiberScheduler.js b/packages/react-reconciler/src/ReactFiberScheduler.js
index c4f3c8aeecf5..91b7916876c8 100644
--- a/packages/react-reconciler/src/ReactFiberScheduler.js
+++ b/packages/react-reconciler/src/ReactFiberScheduler.js
@@ -1474,6 +1474,8 @@ function scheduleWork(fiber: Fiber, expirationTime: ExpirationTime) {
requestWork(root, rootExpirationTime);
}
if (nestedUpdateCount > NESTED_UPDATE_LIMIT) {
+ // Reset this back to zero so subsequent updates don't throw.
+ nestedUpdateCount = 0;
invariant(
false,
'Maximum update depth exceeded. This can happen when a ' +
@@ -1544,6 +1546,7 @@ let currentSchedulerTime: ExpirationTime = currentRendererTime;
// Use these to prevent an infinite loop of nested updates
const NESTED_UPDATE_LIMIT = 1000;
let nestedUpdateCount: number = 0;
+let lastCommittedRootDuringThisBatch: FiberRoot | null = null;
const timeHeuristicForUnitOfWork = 1;
@@ -1798,19 +1801,6 @@ function findHighestPriorityRoot() {
}
}
- // If the next root is the same as the previous root, this is a nested
- // update. To prevent an infinite loop, increment the nested update count.
- const previousFlushedRoot = nextFlushedRoot;
- if (
- previousFlushedRoot !== null &&
- previousFlushedRoot === highestPriorityRoot &&
- highestPriorityWork === Sync
- ) {
- nestedUpdateCount++;
- } else {
- // Reset whenever we switch roots.
- nestedUpdateCount = 0;
- }
nextFlushedRoot = highestPriorityRoot;
nextFlushedExpirationTime = highestPriorityWork;
}
@@ -1911,6 +1901,7 @@ function flushRoot(root: FiberRoot, expirationTime: ExpirationTime) {
function finishRendering() {
nestedUpdateCount = 0;
+ lastCommittedRootDuringThisBatch = null;
if (completedBatches !== null) {
const batches = completedBatches;
@@ -2046,6 +2037,17 @@ function completeRoot(
// Commit the root.
root.finishedWork = null;
+ // Check if this is a nested update (a sync update scheduled during the
+ // commit phase).
+ if (root === lastCommittedRootDuringThisBatch) {
+ // If the next root is the same as the previous root, this is a nested
+ // update. To prevent an infinite loop, increment the nested update count.
+ nestedUpdateCount++;
+ } else {
+ // Reset whenever we switch roots.
+ lastCommittedRootDuringThisBatch = root;
+ nestedUpdateCount = 0;
+ }
commitRoot(root, finishedWork);
}