@@ -1558,37 +1558,102 @@ function validateFunctionComponentInDev(workInProgress: Fiber, Component: any) {
1558
1558
}
1559
1559
}
1560
1560
1561
- const SUSPENDED_MARKER : SuspenseState = {
1562
- dehydrated : null ,
1563
- retryTime : NoWork ,
1564
- } ;
1561
+ function mountSuspenseState (
1562
+ renderExpirationTime : ExpirationTime ,
1563
+ ) : SuspenseState {
1564
+ return {
1565
+ dehydrated : null ,
1566
+ baseTime : renderExpirationTime ,
1567
+ retryTime : NoWork ,
1568
+ } ;
1569
+ }
1570
+
1571
+ function updateSuspenseState (
1572
+ prevSuspenseState : SuspenseState ,
1573
+ renderExpirationTime : ExpirationTime ,
1574
+ ) : SuspenseState {
1575
+ const prevSuspendedTime = prevSuspenseState . baseTime ;
1576
+ return {
1577
+ dehydrated : null ,
1578
+ baseTime :
1579
+ // Choose whichever time is inclusive of the other one. This represents
1580
+ // the union of all the levels that suspended.
1581
+ prevSuspendedTime !== NoWork && prevSuspendedTime < renderExpirationTime
1582
+ ? prevSuspendedTime
1583
+ : renderExpirationTime ,
1584
+ retryTime : NoWork ,
1585
+ } ;
1586
+ }
1565
1587
1566
1588
function shouldRemainOnFallback (
1567
1589
suspenseContext : SuspenseContext ,
1568
1590
current : null | Fiber ,
1569
1591
workInProgress : Fiber ,
1592
+ renderExpirationTime : ExpirationTime ,
1570
1593
) {
1571
- // If the context is telling us that we should show a fallback, and we're not
1572
- // already showing content, then we should show the fallback instead.
1573
- return (
1574
- hasSuspenseContext (
1575
- suspenseContext ,
1576
- ( ForceSuspenseFallback : SuspenseContext ) ,
1577
- ) &&
1578
- ( current === null || current . memoizedState !== null )
1594
+ // If we're already showing a fallback, there are cases where we need to
1595
+ // remain on that fallback regardless of whether the content has resolved.
1596
+ // For example, SuspenseList coordinates when nested content appears.
1597
+ if ( current !== null ) {
1598
+ const suspenseState : SuspenseState = current . memoizedState ;
1599
+ if ( suspenseState !== null ) {
1600
+ // Currently showing a fallback. If the current render includes
1601
+ // the level that triggered the fallback, we must continue showing it,
1602
+ // regardless of what the Suspense context says.
1603
+ const baseTime = suspenseState . baseTime ;
1604
+ if ( baseTime !== NoWork && baseTime < renderExpirationTime ) {
1605
+ return true ;
1606
+ }
1607
+ // Otherwise, fall through to check the Suspense context.
1608
+ } else {
1609
+ // Currently showing content. Don't hide it, even if ForceSuspenseFallack
1610
+ // is true. More precise name might be "ForceRemainSuspenseFallback".
1611
+ // Note: This is a factoring smell. Can't remain on a fallback if there's
1612
+ // no fallback to remain on.
1613
+ return false ;
1614
+ }
1615
+ }
1616
+ // Not currently showing content. Consult the Suspense context.
1617
+ return hasSuspenseContext (
1618
+ suspenseContext ,
1619
+ ( ForceSuspenseFallback : SuspenseContext ) ,
1579
1620
) ;
1580
1621
}
1581
1622
1582
1623
function getRemainingWorkInPrimaryTree (
1583
- workInProgress ,
1584
- currentChildExpirationTime ,
1624
+ current : Fiber ,
1625
+ workInProgress : Fiber ,
1626
+ currentPrimaryChildFragment : Fiber | null ,
1585
1627
renderExpirationTime ,
1586
1628
) {
1629
+ const currentParentOfPrimaryChildren =
1630
+ currentPrimaryChildFragment !== null
1631
+ ? currentPrimaryChildFragment
1632
+ : current ;
1633
+ const currentChildExpirationTime =
1634
+ currentParentOfPrimaryChildren . childExpirationTime ;
1635
+
1636
+ const currentSuspenseState : SuspenseState = current . memoizedState ;
1637
+ if ( currentSuspenseState !== null ) {
1638
+ // This boundary already timed out. Check if this render includes the level
1639
+ // that previously suspended.
1640
+ const baseTime = currentSuspenseState . baseTime ;
1641
+ if (
1642
+ baseTime !== NoWork &&
1643
+ baseTime < renderExpirationTime &&
1644
+ baseTime > currentChildExpirationTime
1645
+ ) {
1646
+ // There's pending work at a lower level that might now be unblocked.
1647
+ return baseTime ;
1648
+ }
1649
+ }
1650
+
1587
1651
if ( currentChildExpirationTime < renderExpirationTime ) {
1588
1652
// The highest priority remaining work is not part of this render. So the
1589
1653
// remaining work has not changed.
1590
1654
return currentChildExpirationTime ;
1591
1655
}
1656
+
1592
1657
if ( ( workInProgress . mode & BlockingMode ) !== NoMode ) {
1593
1658
// The highest priority remaining work is part of this render. Since we only
1594
1659
// keep track of the highest level, we don't know if there's a lower
@@ -1630,7 +1695,12 @@ function updateSuspenseComponent(
1630
1695
1631
1696
if (
1632
1697
didSuspend ||
1633
- shouldRemainOnFallback ( suspenseContext , current , workInProgress )
1698
+ shouldRemainOnFallback (
1699
+ suspenseContext ,
1700
+ current ,
1701
+ workInProgress ,
1702
+ renderExpirationTime ,
1703
+ )
1634
1704
) {
1635
1705
// Something in this boundary's subtree already suspended. Switch to
1636
1706
// rendering the fallback children.
@@ -1746,7 +1816,7 @@ function updateSuspenseComponent(
1746
1816
primaryChildFragment . sibling = fallbackChildFragment ;
1747
1817
// Skip the primary children, and continue working on the
1748
1818
// fallback children.
1749
- workInProgress . memoizedState = SUSPENDED_MARKER ;
1819
+ workInProgress . memoizedState = mountSuspenseState ( renderExpirationTime ) ;
1750
1820
workInProgress . child = primaryChildFragment ;
1751
1821
return fallbackChildFragment ;
1752
1822
} else {
@@ -1850,15 +1920,15 @@ function updateSuspenseComponent(
1850
1920
primaryChildFragment . sibling = fallbackChildFragment ;
1851
1921
fallbackChildFragment . effectTag |= Placement ;
1852
1922
primaryChildFragment . childExpirationTime = getRemainingWorkInPrimaryTree (
1923
+ current ,
1853
1924
workInProgress ,
1854
- // This argument represents the remaining work in the current
1855
- // primary tree. Since the current tree did not already time out
1856
- // the direct parent of the primary children is the Suspense
1857
- // fiber, not a fragment.
1858
- current . childExpirationTime ,
1925
+ null ,
1926
+ renderExpirationTime ,
1927
+ ) ;
1928
+ workInProgress . memoizedState = updateSuspenseState (
1929
+ current . memoizedState ,
1859
1930
renderExpirationTime ,
1860
1931
) ;
1861
- workInProgress . memoizedState = SUSPENDED_MARKER ;
1862
1932
workInProgress . child = primaryChildFragment ;
1863
1933
1864
1934
// Skip the primary children, and continue working on the
@@ -1921,13 +1991,17 @@ function updateSuspenseComponent(
1921
1991
fallbackChildFragment . return = workInProgress ;
1922
1992
primaryChildFragment . sibling = fallbackChildFragment ;
1923
1993
primaryChildFragment . childExpirationTime = getRemainingWorkInPrimaryTree (
1994
+ current ,
1924
1995
workInProgress ,
1925
- currentPrimaryChildFragment . childExpirationTime ,
1996
+ currentPrimaryChildFragment ,
1926
1997
renderExpirationTime ,
1927
1998
) ;
1928
1999
// Skip the primary children, and continue working on the
1929
2000
// fallback children.
1930
- workInProgress . memoizedState = SUSPENDED_MARKER ;
2001
+ workInProgress . memoizedState = updateSuspenseState (
2002
+ current . memoizedState ,
2003
+ renderExpirationTime ,
2004
+ ) ;
1931
2005
workInProgress . child = primaryChildFragment ;
1932
2006
return fallbackChildFragment ;
1933
2007
} else {
@@ -2019,17 +2093,14 @@ function updateSuspenseComponent(
2019
2093
primaryChildFragment . sibling = fallbackChildFragment ;
2020
2094
fallbackChildFragment . effectTag |= Placement ;
2021
2095
primaryChildFragment . childExpirationTime = getRemainingWorkInPrimaryTree (
2096
+ current ,
2022
2097
workInProgress ,
2023
- // This argument represents the remaining work in the current
2024
- // primary tree. Since the current tree did not already time out
2025
- // the direct parent of the primary children is the Suspense
2026
- // fiber, not a fragment.
2027
- current . childExpirationTime ,
2098
+ null ,
2028
2099
renderExpirationTime ,
2029
2100
) ;
2030
2101
// Skip the primary children, and continue working on the
2031
2102
// fallback children.
2032
- workInProgress . memoizedState = SUSPENDED_MARKER ;
2103
+ workInProgress . memoizedState = mountSuspenseState ( renderExpirationTime ) ;
2033
2104
workInProgress . child = primaryChildFragment ;
2034
2105
return fallbackChildFragment ;
2035
2106
} else {
0 commit comments