Skip to content

Commit e1bda91

Browse files
committed
We track nested updates to simulate a stack overflow error and prevent
infinite loops. Every time we commit a tree, we increment a counter. This works if you only have one tree, but if you update many separate trees, it creates a false negative. The fix is to reset the counter whenever we switch trees.
1 parent 1924c28 commit e1bda91

File tree

2 files changed

+35
-4
lines changed

2 files changed

+35
-4
lines changed

src/renderers/__tests__/ReactUpdates-test.js

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1139,6 +1139,30 @@ describe('ReactUpdates', () => {
11391139
expect(ops).toEqual(['Foo', 'Bar', 'Baz']);
11401140
});
11411141

1142+
it('can render ridiculously large number of roots without triggering infinite update loop error', () => {
1143+
class Foo extends React.Component {
1144+
componentDidMount() {
1145+
const limit = 12;
1146+
for (let i = 0; i < limit; i++) {
1147+
if (i < limit - 1) {
1148+
ReactDOM.render(<div />, document.createElement('div'));
1149+
} else {
1150+
ReactDOM.render(<div />, document.createElement('div'), () => {
1151+
// The "nested update limit" error isn't thrown until setState
1152+
this.setState({});
1153+
});
1154+
}
1155+
}
1156+
}
1157+
render() {
1158+
return null;
1159+
}
1160+
}
1161+
1162+
const container = document.createElement('div');
1163+
ReactDOM.render(<Foo />, container);
1164+
});
1165+
11421166
it('does not fall into an infinite update loop', () => {
11431167
class NonTerminating extends React.Component {
11441168
state = {step: 0};

src/renderers/shared/fiber/ReactFiberScheduler.js

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -235,8 +235,9 @@ module.exports = function<T, P, I, TI, PI, C, CX, PL>(
235235
let isUnmounting: boolean = false;
236236

237237
// Use these to prevent an infinite loop of nested updates
238-
const NESTED_UPDATE_LIMIT = 1000;
239-
let nestedUpdateCount = 0;
238+
const NESTED_UPDATE_LIMIT = 10;
239+
let nestedUpdateCount: number = 0;
240+
let nextRenderedTree: FiberRoot | null = null;
240241

241242
function resetContextStack() {
242243
// Reset the stack
@@ -301,11 +302,17 @@ module.exports = function<T, P, I, TI, PI, C, CX, PL>(
301302
highestPriorityRoot.current,
302303
highestPriorityLevel,
303304
);
305+
if (highestPriorityRoot !== nextRenderedTree) {
306+
// We've switched trees. Reset the nested update counter.
307+
nestedUpdateCount = 0;
308+
nextRenderedTree = highestPriorityRoot;
309+
}
304310
return;
305311
}
306312

307313
nextPriorityLevel = NoWork;
308314
nextUnitOfWork = null;
315+
nextRenderedTree = null;
309316
return;
310317
}
311318

@@ -969,8 +976,6 @@ module.exports = function<T, P, I, TI, PI, C, CX, PL>(
969976
);
970977
isPerformingWork = true;
971978

972-
nestedUpdateCount = 0;
973-
974979
// The priority context changes during the render phase. We'll need to
975980
// reset it at the end.
976981
const previousPriorityContext = priorityContext;
@@ -1081,6 +1086,8 @@ module.exports = function<T, P, I, TI, PI, C, CX, PL>(
10811086
firstUncaughtError = null;
10821087
capturedErrors = null;
10831088
failedBoundaries = null;
1089+
nestedUpdateCount = 0;
1090+
10841091
if (__DEV__) {
10851092
stopWorkLoopTimer();
10861093
}

0 commit comments

Comments
 (0)