@@ -15,6 +15,7 @@ let ReactTestUtils;
1515
1616describe ( 'ReactUpdates' , ( ) => {
1717 beforeEach ( ( ) => {
18+ jest . resetModules ( ) ;
1819 React = require ( 'react' ) ;
1920 ReactDOM = require ( 'react-dom' ) ;
2021 ReactTestUtils = require ( 'react-dom/test-utils' ) ;
@@ -1311,6 +1312,46 @@ describe('ReactUpdates', () => {
13111312 ReactDOM . render ( < Foo /> , container ) ;
13121313 } ) ;
13131314
1315+ it ( 'resets the update counter for unrelated updates' , ( ) => {
1316+ const container = document . createElement ( 'div' ) ;
1317+ const ref = React . createRef ( ) ;
1318+
1319+ class EventuallyTerminating extends React . Component {
1320+ state = { step : 0 } ;
1321+ componentDidMount ( ) {
1322+ this . setState ( { step : 1 } ) ;
1323+ }
1324+ componentDidUpdate ( ) {
1325+ if ( this . state . step < limit ) {
1326+ this . setState ( { step : this . state . step + 1 } ) ;
1327+ }
1328+ }
1329+ render ( ) {
1330+ return this . state . step ;
1331+ }
1332+ }
1333+
1334+ let limit = 55 ;
1335+ expect ( ( ) => {
1336+ ReactDOM . render ( < EventuallyTerminating ref = { ref } /> , container ) ;
1337+ } ) . toThrow ( 'Maximum' ) ;
1338+
1339+ // Verify that we don't go over the limit if these updates are unrelated.
1340+ limit -= 10 ;
1341+ ReactDOM . render ( < EventuallyTerminating ref = { ref } /> , container ) ;
1342+ expect ( container . textContent ) . toBe ( limit . toString ( ) ) ;
1343+ ref . current . setState ( { step : 0 } ) ;
1344+ expect ( container . textContent ) . toBe ( limit . toString ( ) ) ;
1345+ ref . current . setState ( { step : 0 } ) ;
1346+ expect ( container . textContent ) . toBe ( limit . toString ( ) ) ;
1347+
1348+ limit += 10 ;
1349+ expect ( ( ) => {
1350+ ref . current . setState ( { step : 0 } ) ;
1351+ } ) . toThrow ( 'Maximum' ) ;
1352+ expect ( ref . current ) . toBe ( null ) ;
1353+ } ) ;
1354+
13141355 it ( 'does not fall into an infinite update loop' , ( ) => {
13151356 class NonTerminating extends React . Component {
13161357 state = { step : 0 } ;
@@ -1336,6 +1377,88 @@ describe('ReactUpdates', () => {
13361377 } ) . toThrow ( 'Maximum' ) ;
13371378 } ) ;
13381379
1380+ it ( 'does not fall into an infinite update loop with useLayoutEffect' , ( ) => {
1381+ function NonTerminating ( ) {
1382+ const [ step , setStep ] = React . useState ( 0 ) ;
1383+ React . useLayoutEffect ( ( ) => {
1384+ setStep ( x => x + 1 ) ;
1385+ } ) ;
1386+ return step ;
1387+ }
1388+
1389+ const container = document . createElement ( 'div' ) ;
1390+ expect ( ( ) => {
1391+ ReactDOM . render ( < NonTerminating /> , container ) ;
1392+ } ) . toThrow ( 'Maximum' ) ;
1393+ } ) ;
1394+
1395+ it ( 'can recover after falling into an infinite update loop' , ( ) => {
1396+ class NonTerminating extends React . Component {
1397+ state = { step : 0 } ;
1398+ componentDidMount ( ) {
1399+ this . setState ( { step : 1 } ) ;
1400+ }
1401+ componentDidUpdate ( ) {
1402+ this . setState ( { step : 2 } ) ;
1403+ }
1404+ render ( ) {
1405+ return this . state . step ;
1406+ }
1407+ }
1408+
1409+ class Terminating extends React . Component {
1410+ state = { step : 0 } ;
1411+ componentDidMount ( ) {
1412+ this . setState ( { step : 1 } ) ;
1413+ }
1414+ render ( ) {
1415+ return this . state . step ;
1416+ }
1417+ }
1418+
1419+ const container = document . createElement ( 'div' ) ;
1420+ expect ( ( ) => {
1421+ ReactDOM . render ( < NonTerminating /> , container ) ;
1422+ } ) . toThrow ( 'Maximum' ) ;
1423+
1424+ ReactDOM . render ( < Terminating /> , container ) ;
1425+ expect ( container . textContent ) . toBe ( '1' ) ;
1426+
1427+ expect ( ( ) => {
1428+ ReactDOM . render ( < NonTerminating /> , container ) ;
1429+ } ) . toThrow ( 'Maximum' ) ;
1430+
1431+ ReactDOM . render ( < Terminating /> , container ) ;
1432+ expect ( container . textContent ) . toBe ( '1' ) ;
1433+ } ) ;
1434+
1435+ it ( 'does not fall into mutually recursive infinite update loop with same container' , ( ) => {
1436+ // Note: this test would fail if there were two or more different roots.
1437+
1438+ class A extends React . Component {
1439+ componentDidMount ( ) {
1440+ ReactDOM . render ( < B /> , container ) ;
1441+ }
1442+ render ( ) {
1443+ return null ;
1444+ }
1445+ }
1446+
1447+ class B extends React . Component {
1448+ componentDidMount ( ) {
1449+ ReactDOM . render ( < A /> , container ) ;
1450+ }
1451+ render ( ) {
1452+ return null ;
1453+ }
1454+ }
1455+
1456+ const container = document . createElement ( 'div' ) ;
1457+ expect ( ( ) => {
1458+ ReactDOM . render ( < A /> , container ) ;
1459+ } ) . toThrow ( 'Maximum' ) ;
1460+ } ) ;
1461+
13391462 it ( 'does not fall into an infinite error loop' , ( ) => {
13401463 function BadRender ( ) {
13411464 throw new Error ( 'error' ) ;
0 commit comments