@@ -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