Track nested updates per root (#10574)
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.
Andrew Clark committed
Aug 30, 2017 at 18:17 UTC
a6e34cc25f34658798af0c3c97e5898cabf9dc4f
2 files changed
+35
-3
src/renderers/__tests__/ReactUpdates-test.js
+24
@@ -1139,6 +1139,30 @@ describe('ReactUpdates', () => {
1139
expect(ops).toEqual(['Foo', 'Bar', 'Baz']);
1140
});
1141
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 = 1200;
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
+
1166
it('does not fall into an infinite update loop', () => {
1167
class NonTerminating extends React.Component {
1168
state = {step: 0};
src/renderers/shared/fiber/ReactFiberScheduler.js
+11
-3
@@ -236,7 +236,8 @@ module.exports = function<T, P, I, TI, PI, C, CX, PL>(
236
237
// Use these to prevent an infinite loop of nested updates
238
const NESTED_UPDATE_LIMIT = 1000;
239
- let nestedUpdateCount = 0;
239
+ let nestedUpdateCount: number = 0;
240
+ let nextRenderedTree: FiberRoot | null = null;
241
242
function resetContextStack() {
243
// Reset the stack
@@ -301,11 +302,17 @@ module.exports = function<T, P, I, TI, PI, C, CX, PL>(
302
highestPriorityRoot.current,
303
highestPriorityLevel,
304
);
305
+ if (highestPriorityRoot !== nextRenderedTree) {
306
+ // We've switched trees. Reset the nested update counter.
307
+ nestedUpdateCount = 0;
308
+ nextRenderedTree = highestPriorityRoot;
309
+ }
310
return;
311
}
312
313
nextPriorityLevel = NoWork;
314
nextUnitOfWork = null;
315
+ nextRenderedTree = null;
316
return;
317
}
318
@@ -969,8 +976,6 @@ module.exports = function<T, P, I, TI, PI, C, CX, PL>(
976
);
977
isPerformingWork = true;
978
972
- nestedUpdateCount = 0;
973
-
979
// The priority context changes during the render phase. We'll need to
980
// reset it at the end.
981
const previousPriorityContext = priorityContext;
@@ -1081,6 +1086,9 @@ module.exports = function<T, P, I, TI, PI, C, CX, PL>(
1086
firstUncaughtError = null;
1087
capturedErrors = null;
1088
failedBoundaries = null;
1089
+ nextRenderedTree = null;
1090
+ nestedUpdateCount = 0;
1091
+
1092
if (__DEV__) {
1093
stopWorkLoopTimer();
1094
}