From a6e34cc25f34658798af0c3c97e5898cabf9dc4f Mon Sep 17 00:00:00 2001 From: Andrew Clark Date: Wed, 30 Aug 2017 18:17:48 -0700 Subject: [PATCH] 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. --- src/renderers/__tests__/ReactUpdates-test.js | 24 +++++++++++++++++++ .../shared/fiber/ReactFiberScheduler.js | 14 ++++++++--- 2 files changed, 35 insertions(+), 3 deletions(-) diff --git a/src/renderers/__tests__/ReactUpdates-test.js b/src/renderers/__tests__/ReactUpdates-test.js index 14fe5109d59cc..16812b3aba9dd 100644 --- a/src/renderers/__tests__/ReactUpdates-test.js +++ b/src/renderers/__tests__/ReactUpdates-test.js @@ -1139,6 +1139,30 @@ describe('ReactUpdates', () => { expect(ops).toEqual(['Foo', 'Bar', 'Baz']); }); + it('can render ridiculously large number of roots without triggering infinite update loop error', () => { + class Foo extends React.Component { + componentDidMount() { + const limit = 1200; + for (let i = 0; i < limit; i++) { + if (i < limit - 1) { + ReactDOM.render(
, document.createElement('div')); + } else { + ReactDOM.render(
, document.createElement('div'), () => { + // The "nested update limit" error isn't thrown until setState + this.setState({}); + }); + } + } + } + render() { + return null; + } + } + + const container = document.createElement('div'); + ReactDOM.render(, container); + }); + it('does not fall into an infinite update loop', () => { class NonTerminating extends React.Component { state = {step: 0}; diff --git a/src/renderers/shared/fiber/ReactFiberScheduler.js b/src/renderers/shared/fiber/ReactFiberScheduler.js index 6be2c5fda86ff..cda0f0a1a0df7 100644 --- a/src/renderers/shared/fiber/ReactFiberScheduler.js +++ b/src/renderers/shared/fiber/ReactFiberScheduler.js @@ -236,7 +236,8 @@ module.exports = function( // Use these to prevent an infinite loop of nested updates const NESTED_UPDATE_LIMIT = 1000; - let nestedUpdateCount = 0; + let nestedUpdateCount: number = 0; + let nextRenderedTree: FiberRoot | null = null; function resetContextStack() { // Reset the stack @@ -301,11 +302,17 @@ module.exports = function( highestPriorityRoot.current, highestPriorityLevel, ); + if (highestPriorityRoot !== nextRenderedTree) { + // We've switched trees. Reset the nested update counter. + nestedUpdateCount = 0; + nextRenderedTree = highestPriorityRoot; + } return; } nextPriorityLevel = NoWork; nextUnitOfWork = null; + nextRenderedTree = null; return; } @@ -969,8 +976,6 @@ module.exports = function( ); isPerformingWork = true; - nestedUpdateCount = 0; - // The priority context changes during the render phase. We'll need to // reset it at the end. const previousPriorityContext = priorityContext; @@ -1081,6 +1086,9 @@ module.exports = function( firstUncaughtError = null; capturedErrors = null; failedBoundaries = null; + nextRenderedTree = null; + nestedUpdateCount = 0; + if (__DEV__) { stopWorkLoopTimer(); }