@samitouri / QOS-React / commits / a6e34cc25f

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 }