@@ -187,6 +187,7 @@ import {
187
187
renderDidSuspendDelayIfPossible ,
188
188
markUnprocessedUpdateTime ,
189
189
getWorkInProgressRoot ,
190
+ pushRenderExpirationTime ,
190
191
} from './ReactFiberWorkLoop.new' ;
191
192
192
193
import { disableLogs , reenableLogs } from 'shared/ConsolePatchingDev' ;
@@ -569,17 +570,30 @@ function updateOffscreenComponent(
569
570
const nextProps : OffscreenProps = workInProgress . pendingProps ;
570
571
const nextChildren = nextProps . children ;
571
572
573
+ let subtreeRenderTime = renderExpirationTime ;
572
574
if ( current !== null ) {
573
575
if ( nextProps . mode === 'hidden' ) {
574
576
// TODO: Should currently be unreachable because Offscreen is only used as
575
577
// an implementation detail of Suspense. Once this is a public API, it
576
578
// will need to create an OffscreenState.
577
579
} else {
578
- // Clear the offscreen state.
579
- workInProgress . memoizedState = null ;
580
+ const prevState : OffscreenState | null = current . memoizedState ;
581
+ if ( prevState !== null ) {
582
+ const baseTime = prevState . baseTime ;
583
+ subtreeRenderTime = ! isSameOrHigherPriority (
584
+ baseTime ,
585
+ renderExpirationTime ,
586
+ )
587
+ ? baseTime
588
+ : renderExpirationTime ;
589
+
590
+ // Since we're not hidden anymore, reset the state
591
+ workInProgress . memoizedState = null ;
592
+ }
580
593
}
581
594
}
582
595
596
+ pushRenderExpirationTime ( workInProgress , subtreeRenderTime ) ;
583
597
reconcileChildren (
584
598
current ,
585
599
workInProgress ,
@@ -1651,33 +1665,32 @@ function validateFunctionComponentInDev(workInProgress: Fiber, Component: any) {
1651
1665
}
1652
1666
}
1653
1667
1654
- function mountSuspenseState (
1668
+ const SUSPENDED_MARKER : SuspenseState = {
1669
+ dehydrated : null ,
1670
+ retryTime : NoWork ,
1671
+ } ;
1672
+
1673
+ function mountSuspenseOffscreenState (
1655
1674
renderExpirationTime : ExpirationTimeOpaque ,
1656
- ) : SuspenseState {
1675
+ ) : OffscreenState {
1657
1676
return {
1658
- dehydrated : null ,
1659
1677
baseTime : renderExpirationTime ,
1660
- retryTime : NoWork ,
1661
1678
} ;
1662
1679
}
1663
1680
1664
- function updateSuspenseState (
1665
- prevSuspenseState : SuspenseState ,
1681
+ function updateSuspenseOffscreenState (
1682
+ prevOffscreenState : OffscreenState ,
1666
1683
renderExpirationTime : ExpirationTimeOpaque ,
1667
- ) : SuspenseState {
1668
- const prevSuspendedTime = prevSuspenseState . baseTime ;
1684
+ ) : OffscreenState {
1685
+ const prevBaseTime = prevOffscreenState . baseTime ;
1669
1686
return {
1670
- dehydrated : null ,
1687
+ // Choose whichever time is inclusive of the other one. This represents
1688
+ // the union of all the levels that suspended.
1671
1689
baseTime :
1672
- // Choose whichever time is inclusive of the other one. This represents
1673
- // the union of all the levels that suspended.
1674
- ! isSameExpirationTime (
1675
- prevSuspendedTime ,
1676
- ( NoWork : ExpirationTimeOpaque ) ,
1677
- ) && ! isSameOrHigherPriority ( prevSuspendedTime , renderExpirationTime )
1678
- ? prevSuspendedTime
1690
+ ! isSameExpirationTime ( prevBaseTime , ( NoWork : ExpirationTimeOpaque ) ) &&
1691
+ ! isSameOrHigherPriority ( prevBaseTime , renderExpirationTime )
1692
+ ? prevBaseTime
1679
1693
: renderExpirationTime ,
1680
- retryTime : NoWork ,
1681
1694
} ;
1682
1695
}
1683
1696
@@ -1692,26 +1705,15 @@ function shouldRemainOnFallback(
1692
1705
// For example, SuspenseList coordinates when nested content appears.
1693
1706
if ( current !== null ) {
1694
1707
const suspenseState : SuspenseState = current . memoizedState ;
1695
- if ( suspenseState !== null ) {
1696
- // Currently showing a fallback. If the current render includes
1697
- // the level that triggered the fallback, we must continue showing it,
1698
- // regardless of what the Suspense context says.
1699
- const baseTime = suspenseState . baseTime ;
1700
- if (
1701
- ! isSameExpirationTime ( baseTime , ( NoWork : ExpirationTimeOpaque ) ) &&
1702
- ! isSameOrHigherPriority ( baseTime , renderExpirationTime )
1703
- ) {
1704
- return true ;
1705
- }
1706
- // Otherwise, fall through to check the Suspense context.
1707
- } else {
1708
+ if ( suspenseState === null ) {
1708
1709
// Currently showing content. Don't hide it, even if ForceSuspenseFallack
1709
1710
// is true. More precise name might be "ForceRemainSuspenseFallback".
1710
1711
// Note: This is a factoring smell. Can't remain on a fallback if there's
1711
1712
// no fallback to remain on.
1712
1713
return false ;
1713
1714
}
1714
1715
}
1716
+
1715
1717
// Not currently showing content. Consult the Suspense context.
1716
1718
return hasSuspenseContext (
1717
1719
suspenseContext ,
@@ -1725,20 +1727,6 @@ function getRemainingWorkInPrimaryTree(
1725
1727
renderExpirationTime ,
1726
1728
) {
1727
1729
const currentChildExpirationTime = current . childExpirationTime_opaque ;
1728
- const currentSuspenseState : SuspenseState = current . memoizedState ;
1729
- if ( currentSuspenseState !== null ) {
1730
- // This boundary already timed out. Check if this render includes the level
1731
- // that previously suspended.
1732
- const baseTime = currentSuspenseState . baseTime ;
1733
- if (
1734
- ! isSameExpirationTime ( baseTime , ( NoWork : ExpirationTimeOpaque ) ) &&
1735
- ! isSameOrHigherPriority ( baseTime , renderExpirationTime )
1736
- ) {
1737
- // There's pending work at a lower level that might now be unblocked.
1738
- return baseTime ;
1739
- }
1740
- }
1741
-
1742
1730
if (
1743
1731
! isSameOrHigherPriority ( currentChildExpirationTime , renderExpirationTime )
1744
1732
) {
@@ -1880,8 +1868,10 @@ function updateSuspenseComponent(
1880
1868
renderExpirationTime ,
1881
1869
) ;
1882
1870
const primaryChildFragment : Fiber = ( workInProgress . child : any ) ;
1883
- primaryChildFragment . memoizedState = ( { baseTime : NoWork } : OffscreenState ) ;
1884
- workInProgress . memoizedState = mountSuspenseState ( renderExpirationTime ) ;
1871
+ primaryChildFragment . memoizedState = mountSuspenseOffscreenState (
1872
+ renderExpirationTime ,
1873
+ ) ;
1874
+ workInProgress . memoizedState = SUSPENDED_MARKER ;
1885
1875
return fallbackFragment ;
1886
1876
} else {
1887
1877
const nextPrimaryChildren = nextProps . children ;
@@ -1935,14 +1925,10 @@ function updateSuspenseComponent(
1935
1925
renderExpirationTime ,
1936
1926
) ;
1937
1927
const primaryChildFragment : Fiber = ( workInProgress . child : any ) ;
1938
- primaryChildFragment . memoizedState = ( {
1939
- baseTime : NoWork ,
1940
- } : OffscreenState ) ;
1941
- workInProgress . memoizedState = updateSuspenseState (
1942
- current . memoizedState ,
1928
+ primaryChildFragment . memoizedState = mountSuspenseOffscreenState (
1943
1929
renderExpirationTime ,
1944
1930
) ;
1945
-
1931
+ workInProgress . memoizedState = SUSPENDED_MARKER ;
1946
1932
return fallbackChildFragment ;
1947
1933
}
1948
1934
}
@@ -1959,18 +1945,21 @@ function updateSuspenseComponent(
1959
1945
renderExpirationTime ,
1960
1946
) ;
1961
1947
const primaryChildFragment : Fiber = ( workInProgress . child : any ) ;
1962
- primaryChildFragment . memoizedState = ( {
1963
- baseTime : NoWork ,
1964
- } : OffscreenState ) ;
1948
+ const prevOffscreenState : OffscreenState | null = ( current . child : any )
1949
+ . memoizedState ;
1950
+ primaryChildFragment . memoizedState =
1951
+ prevOffscreenState === null
1952
+ ? mountSuspenseOffscreenState ( renderExpirationTime )
1953
+ : updateSuspenseOffscreenState (
1954
+ prevOffscreenState ,
1955
+ renderExpirationTime ,
1956
+ ) ;
1965
1957
primaryChildFragment . childExpirationTime_opaque = getRemainingWorkInPrimaryTree (
1966
1958
current ,
1967
1959
workInProgress ,
1968
1960
renderExpirationTime ,
1969
1961
) ;
1970
- workInProgress . memoizedState = updateSuspenseState (
1971
- current . memoizedState ,
1972
- renderExpirationTime ,
1973
- ) ;
1962
+ workInProgress . memoizedState = SUSPENDED_MARKER ;
1974
1963
return fallbackChildFragment ;
1975
1964
} else {
1976
1965
const nextPrimaryChildren = nextProps . children ;
@@ -1997,17 +1986,23 @@ function updateSuspenseComponent(
1997
1986
renderExpirationTime ,
1998
1987
) ;
1999
1988
const primaryChildFragment : Fiber = ( workInProgress . child : any ) ;
2000
- primaryChildFragment . memoizedState = ( {
2001
- baseTime : NoWork ,
2002
- } : OffscreenState ) ;
1989
+ const prevOffscreenState : OffscreenState | null = ( current . child : any )
1990
+ . memoizedState ;
1991
+ primaryChildFragment . memoizedState =
1992
+ prevOffscreenState === null
1993
+ ? mountSuspenseOffscreenState ( renderExpirationTime )
1994
+ : updateSuspenseOffscreenState (
1995
+ prevOffscreenState ,
1996
+ renderExpirationTime ,
1997
+ ) ;
2003
1998
primaryChildFragment . childExpirationTime_opaque = getRemainingWorkInPrimaryTree (
2004
1999
current ,
2005
2000
workInProgress ,
2006
2001
renderExpirationTime ,
2007
2002
) ;
2008
2003
// Skip the primary children, and continue working on the
2009
2004
// fallback children.
2010
- workInProgress . memoizedState = mountSuspenseState ( renderExpirationTime ) ;
2005
+ workInProgress . memoizedState = SUSPENDED_MARKER ;
2011
2006
return fallbackChildFragment ;
2012
2007
} else {
2013
2008
// Still haven't timed out. Continue rendering the children, like we
@@ -3384,6 +3379,10 @@ function beginWork(
3384
3379
return null ;
3385
3380
}
3386
3381
}
3382
+ case OffscreenComponent: {
3383
+ pushRenderExpirationTime ( workInProgress , renderExpirationTime ) ;
3384
+ break ;
3385
+ }
3387
3386
}
3388
3387
return bailoutOnAlreadyFinishedWork (
3389
3388
current ,
0 commit comments