@@ -1461,4 +1461,184 @@ describe('ReactAsyncActions', () => {
1461
1461
expect ( root ) . toMatchRenderedOutput ( < span > C</ span > ) ;
1462
1462
} ,
1463
1463
) ;
1464
+
1465
+ // @gate enableAsyncActions
1466
+ test (
1467
+ 'updates in an async action are entangled even if useTransition hook ' +
1468
+ 'is unmounted before it finishes (class component)' ,
1469
+ async ( ) => {
1470
+ let startTransition ;
1471
+ function Updater ( ) {
1472
+ const [ isPending , _start ] = useTransition ( ) ;
1473
+ startTransition = _start ;
1474
+ return (
1475
+ < span >
1476
+ < Text text = { 'Pending: ' + isPending } />
1477
+ </ span >
1478
+ ) ;
1479
+ }
1480
+
1481
+ let setText ;
1482
+ class Sibling extends React . Component {
1483
+ state = { text : 'A' } ;
1484
+ render ( ) {
1485
+ setText = text => this . setState ( { text} ) ;
1486
+ return (
1487
+ < span >
1488
+ < Text text = { this . state . text } />
1489
+ </ span >
1490
+ ) ;
1491
+ }
1492
+ }
1493
+
1494
+ function App ( { showUpdater} ) {
1495
+ return (
1496
+ < >
1497
+ { showUpdater ? < Updater /> : null }
1498
+ < Sibling />
1499
+ </ >
1500
+ ) ;
1501
+ }
1502
+
1503
+ const root = ReactNoop . createRoot ( ) ;
1504
+ await act ( ( ) => {
1505
+ root . render ( < App showUpdater = { true } /> ) ;
1506
+ } ) ;
1507
+ assertLog ( [ 'Pending: false' , 'A' ] ) ;
1508
+ expect ( root ) . toMatchRenderedOutput (
1509
+ < >
1510
+ < span > Pending: false</ span >
1511
+ < span > A</ span >
1512
+ </ > ,
1513
+ ) ;
1514
+
1515
+ // Start an async action that has multiple updates with async
1516
+ // operations in between.
1517
+ await act ( ( ) => {
1518
+ startTransition ( async ( ) => {
1519
+ Scheduler . log ( 'Async action started' ) ;
1520
+ startTransition ( ( ) => setText ( 'B' ) ) ;
1521
+
1522
+ await getText ( 'Wait before updating to C' ) ;
1523
+
1524
+ Scheduler . log ( 'Async action ended' ) ;
1525
+ startTransition ( ( ) => setText ( 'C' ) ) ;
1526
+ } ) ;
1527
+ } ) ;
1528
+ assertLog ( [ 'Async action started' , 'Pending: true' ] ) ;
1529
+ expect ( root ) . toMatchRenderedOutput (
1530
+ < >
1531
+ < span > Pending: true</ span >
1532
+ < span > A</ span >
1533
+ </ > ,
1534
+ ) ;
1535
+
1536
+ // Delete the component that contains the useTransition hook. This
1537
+ // component no longer blocks the transition from completing. But the
1538
+ // pending update to Sibling should not be allowed to finish, because it's
1539
+ // part of the async action.
1540
+ await act ( ( ) => {
1541
+ root . render ( < App showUpdater = { false } /> ) ;
1542
+ } ) ;
1543
+ assertLog ( [ 'A' ] ) ;
1544
+ expect ( root ) . toMatchRenderedOutput ( < span > A</ span > ) ;
1545
+
1546
+ // Finish the async action. Notice the intermediate B state was never
1547
+ // shown, because it was batched with the update that came later in the
1548
+ // same action.
1549
+ await act ( ( ) => resolveText ( 'Wait before updating to C' ) ) ;
1550
+ assertLog ( [ 'Async action ended' , 'C' ] ) ;
1551
+ expect ( root ) . toMatchRenderedOutput ( < span > C</ span > ) ;
1552
+
1553
+ // Check that subsequent updates are unaffected.
1554
+ await act ( ( ) => setText ( 'D' ) ) ;
1555
+ assertLog ( [ 'D' ] ) ;
1556
+ expect ( root ) . toMatchRenderedOutput ( < span > D</ span > ) ;
1557
+ } ,
1558
+ ) ;
1559
+
1560
+ // @gate enableAsyncActions
1561
+ test (
1562
+ 'updates in an async action are entangled even if useTransition hook ' +
1563
+ 'is unmounted before it finishes (root update)' ,
1564
+ async ( ) => {
1565
+ let startTransition ;
1566
+ function Updater ( ) {
1567
+ const [ isPending , _start ] = useTransition ( ) ;
1568
+ startTransition = _start ;
1569
+ return (
1570
+ < span >
1571
+ < Text text = { 'Pending: ' + isPending } />
1572
+ </ span >
1573
+ ) ;
1574
+ }
1575
+
1576
+ let setShowUpdater ;
1577
+ function App ( { text} ) {
1578
+ const [ showUpdater , _setShowUpdater ] = useState ( true ) ;
1579
+ setShowUpdater = _setShowUpdater ;
1580
+ return (
1581
+ < >
1582
+ { showUpdater ? < Updater /> : null }
1583
+ < span >
1584
+ < Text text = { text } />
1585
+ </ span >
1586
+ </ >
1587
+ ) ;
1588
+ }
1589
+
1590
+ const root = ReactNoop . createRoot ( ) ;
1591
+ await act ( ( ) => {
1592
+ root . render ( < App text = "A" /> ) ;
1593
+ } ) ;
1594
+ assertLog ( [ 'Pending: false' , 'A' ] ) ;
1595
+ expect ( root ) . toMatchRenderedOutput (
1596
+ < >
1597
+ < span > Pending: false</ span >
1598
+ < span > A</ span >
1599
+ </ > ,
1600
+ ) ;
1601
+
1602
+ // Start an async action that has multiple updates with async
1603
+ // operations in between.
1604
+ await act ( ( ) => {
1605
+ startTransition ( async ( ) => {
1606
+ Scheduler . log ( 'Async action started' ) ;
1607
+ startTransition ( ( ) => root . render ( < App text = "B" /> ) ) ;
1608
+
1609
+ await getText ( 'Wait before updating to C' ) ;
1610
+
1611
+ Scheduler . log ( 'Async action ended' ) ;
1612
+ startTransition ( ( ) => root . render ( < App text = "C" /> ) ) ;
1613
+ } ) ;
1614
+ } ) ;
1615
+ assertLog ( [ 'Async action started' , 'Pending: true' ] ) ;
1616
+ expect ( root ) . toMatchRenderedOutput (
1617
+ < >
1618
+ < span > Pending: true</ span >
1619
+ < span > A</ span >
1620
+ </ > ,
1621
+ ) ;
1622
+
1623
+ // Delete the component that contains the useTransition hook. This
1624
+ // component no longer blocks the transition from completing. But the
1625
+ // pending update to Sibling should not be allowed to finish, because it's
1626
+ // part of the async action.
1627
+ await act ( ( ) => setShowUpdater ( false ) ) ;
1628
+ assertLog ( [ 'A' ] ) ;
1629
+ expect ( root ) . toMatchRenderedOutput ( < span > A</ span > ) ;
1630
+
1631
+ // Finish the async action. Notice the intermediate B state was never
1632
+ // shown, because it was batched with the update that came later in the
1633
+ // same action.
1634
+ await act ( ( ) => resolveText ( 'Wait before updating to C' ) ) ;
1635
+ assertLog ( [ 'Async action ended' , 'C' ] ) ;
1636
+ expect ( root ) . toMatchRenderedOutput ( < span > C</ span > ) ;
1637
+
1638
+ // Check that subsequent updates are unaffected.
1639
+ await act ( ( ) => root . render ( < App text = "D" /> ) ) ;
1640
+ assertLog ( [ 'D' ] ) ;
1641
+ expect ( root ) . toMatchRenderedOutput ( < span > D</ span > ) ;
1642
+ } ,
1643
+ ) ;
1464
1644
} ) ;
0 commit comments