@@ -15,6 +15,7 @@ let ReactTestUtils;
15
15
16
16
describe ( 'ReactUpdates' , ( ) => {
17
17
beforeEach ( ( ) => {
18
+ jest . resetModules ( ) ;
18
19
React = require ( 'react' ) ;
19
20
ReactDOM = require ( 'react-dom' ) ;
20
21
ReactTestUtils = require ( 'react-dom/test-utils' ) ;
@@ -1311,6 +1312,46 @@ describe('ReactUpdates', () => {
1311
1312
ReactDOM . render ( < Foo /> , container ) ;
1312
1313
} ) ;
1313
1314
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
+
1314
1355
it ( 'does not fall into an infinite update loop' , ( ) => {
1315
1356
class NonTerminating extends React . Component {
1316
1357
state = { step : 0 } ;
@@ -1336,6 +1377,88 @@ describe('ReactUpdates', () => {
1336
1377
} ) . toThrow ( 'Maximum' ) ;
1337
1378
} ) ;
1338
1379
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
+
1339
1462
it ( 'does not fall into an infinite error loop' , ( ) => {
1340
1463
function BadRender ( ) {
1341
1464
throw new Error ( 'error' ) ;
0 commit comments