@@ -175,6 +175,7 @@ import {
175
175
} from './ReactEventPriorities' ;
176
176
import { requestCurrentTransition , NoTransition } from './ReactFiberTransition' ;
177
177
import {
178
+ SelectiveHydrationException ,
178
179
beginWork as originalBeginWork ,
179
180
replayFunctionComponent ,
180
181
} from './ReactFiberBeginWork' ;
@@ -316,13 +317,14 @@ let workInProgress: Fiber | null = null;
316
317
// The lanes we're rendering
317
318
let workInProgressRootRenderLanes : Lanes = NoLanes ;
318
319
319
- opaque type SuspendedReason = 0 | 1 | 2 | 3 | 4 | 5 ;
320
+ opaque type SuspendedReason = 0 | 1 | 2 | 3 | 4 | 5 | 6 ;
320
321
const NotSuspended : SuspendedReason = 0 ;
321
322
const SuspendedOnError : SuspendedReason = 1 ;
322
323
const SuspendedOnData : SuspendedReason = 2 ;
323
324
const SuspendedOnImmediate : SuspendedReason = 3 ;
324
325
const SuspendedOnDeprecatedThrowPromise : SuspendedReason = 4 ;
325
326
const SuspendedAndReadyToUnwind : SuspendedReason = 5 ;
327
+ const SuspendedOnHydration : SuspendedReason = 6 ;
326
328
327
329
// When this is true, the work-in-progress fiber just suspended (or errored) and
328
330
// we've yet to unwind the stack. In some cases, we may yield to the main thread
@@ -1701,6 +1703,31 @@ export function getRenderLanes(): Lanes {
1701
1703
return renderLanes ;
1702
1704
}
1703
1705
1706
+ function resetWorkInProgressStack ( ) {
1707
+ if ( workInProgress === null ) return ;
1708
+ let interruptedWork ;
1709
+ if ( workInProgressSuspendedReason === NotSuspended ) {
1710
+ // Normal case. Work-in-progress hasn't started yet. Unwind all
1711
+ // its parents.
1712
+ interruptedWork = workInProgress . return ;
1713
+ } else {
1714
+ // Work-in-progress is in suspended state. Reset the work loop and unwind
1715
+ // both the suspended fiber and all its parents.
1716
+ resetSuspendedWorkLoopOnUnwind ( ) ;
1717
+ interruptedWork = workInProgress ;
1718
+ }
1719
+ while ( interruptedWork !== null ) {
1720
+ const current = interruptedWork . alternate ;
1721
+ unwindInterruptedWork (
1722
+ current ,
1723
+ interruptedWork ,
1724
+ workInProgressRootRenderLanes ,
1725
+ ) ;
1726
+ interruptedWork = interruptedWork . return ;
1727
+ }
1728
+ workInProgress = null ;
1729
+ }
1730
+
1704
1731
function prepareFreshStack ( root : FiberRoot , lanes : Lanes ) : Fiber {
1705
1732
root . finishedWork = null ;
1706
1733
root . finishedLanes = NoLanes ;
@@ -1714,28 +1741,7 @@ function prepareFreshStack(root: FiberRoot, lanes: Lanes): Fiber {
1714
1741
cancelTimeout ( timeoutHandle ) ;
1715
1742
}
1716
1743
1717
- if ( workInProgress !== null ) {
1718
- let interruptedWork ;
1719
- if ( workInProgressSuspendedReason === NotSuspended ) {
1720
- // Normal case. Work-in-progress hasn't started yet. Unwind all
1721
- // its parents.
1722
- interruptedWork = workInProgress . return ;
1723
- } else {
1724
- // Work-in-progress is in suspended state. Reset the work loop and unwind
1725
- // both the suspended fiber and all its parents.
1726
- resetSuspendedWorkLoopOnUnwind ( ) ;
1727
- interruptedWork = workInProgress ;
1728
- }
1729
- while ( interruptedWork !== null ) {
1730
- const current = interruptedWork . alternate ;
1731
- unwindInterruptedWork (
1732
- current ,
1733
- interruptedWork ,
1734
- workInProgressRootRenderLanes ,
1735
- ) ;
1736
- interruptedWork = interruptedWork . return ;
1737
- }
1738
- }
1744
+ resetWorkInProgressStack ( ) ;
1739
1745
workInProgressRoot = root ;
1740
1746
const rootWorkInProgress = createWorkInProgress ( root . current , null ) ;
1741
1747
workInProgress = rootWorkInProgress ;
@@ -1797,6 +1803,17 @@ function handleThrow(root, thrownValue): void {
1797
1803
workInProgressSuspendedReason = shouldAttemptToSuspendUntilDataResolves ( )
1798
1804
? SuspendedOnData
1799
1805
: SuspendedOnImmediate ;
1806
+ } else if ( thrownValue === SelectiveHydrationException ) {
1807
+ // An update flowed into a dehydrated boundary. Before we can apply the
1808
+ // update, we need to finish hydrating. Interrupt the work-in-progress
1809
+ // render so we can restart at the hydration lane.
1810
+ //
1811
+ // The ideal implementation would be able to switch contexts without
1812
+ // unwinding the current stack.
1813
+ //
1814
+ // We could name this something more general but as of now it's the only
1815
+ // case where we think this should happen.
1816
+ workInProgressSuspendedReason = SuspendedOnHydration ;
1800
1817
} else {
1801
1818
// This is a regular error.
1802
1819
const isWakeable =
@@ -2038,7 +2055,7 @@ function renderRootSync(root: FiberRoot, lanes: Lanes) {
2038
2055
markRenderStarted ( lanes ) ;
2039
2056
}
2040
2057
2041
- do {
2058
+ outer: do {
2042
2059
try {
2043
2060
if (
2044
2061
workInProgressSuspendedReason !== NotSuspended &&
@@ -2054,11 +2071,23 @@ function renderRootSync(root: FiberRoot, lanes: Lanes) {
2054
2071
// function and fork the behavior some other way.
2055
2072
const unitOfWork = workInProgress ;
2056
2073
const thrownValue = workInProgressThrownValue ;
2057
- workInProgressSuspendedReason = NotSuspended ;
2058
- workInProgressThrownValue = null ;
2059
- unwindSuspendedUnitOfWork ( unitOfWork , thrownValue ) ;
2060
-
2061
- // Continue with the normal work loop.
2074
+ switch ( workInProgressSuspendedReason ) {
2075
+ case SuspendedOnHydration : {
2076
+ // Selective hydration. An update flowed into a dehydrated tree.
2077
+ // Interrupt the current render so the work loop can switch to the
2078
+ // hydration lane.
2079
+ resetWorkInProgressStack ( ) ;
2080
+ workInProgressRootExitStatus = RootDidNotComplete ;
2081
+ break outer;
2082
+ }
2083
+ default : {
2084
+ // Continue with the normal work loop.
2085
+ workInProgressSuspendedReason = NotSuspended ;
2086
+ workInProgressThrownValue = null ;
2087
+ unwindSuspendedUnitOfWork ( unitOfWork , thrownValue ) ;
2088
+ break ;
2089
+ }
2090
+ }
2062
2091
}
2063
2092
workLoopSync ( ) ;
2064
2093
break ;
@@ -2216,6 +2245,14 @@ function renderRootConcurrent(root: FiberRoot, lanes: Lanes) {
2216
2245
unwindSuspendedUnitOfWork ( unitOfWork , thrownValue ) ;
2217
2246
break ;
2218
2247
}
2248
+ case SuspendedOnHydration : {
2249
+ // Selective hydration. An update flowed into a dehydrated tree.
2250
+ // Interrupt the current render so the work loop can switch to the
2251
+ // hydration lane.
2252
+ resetWorkInProgressStack ( ) ;
2253
+ workInProgressRootExitStatus = RootDidNotComplete ;
2254
+ break outer ;
2255
+ }
2219
2256
default : {
2220
2257
throw new Error (
2221
2258
'Unexpected SuspendedReason. This is a bug in React.' ,
@@ -3741,6 +3778,7 @@ if (__DEV__ && replayFailedUnitOfWorkWithInvokeGuardedCallback) {
3741
3778
if (
3742
3779
didSuspendOrErrorWhileHydratingDEV ( ) ||
3743
3780
originalError === SuspenseException ||
3781
+ originalError === SelectiveHydrationException ||
3744
3782
( originalError !== null &&
3745
3783
typeof originalError === 'object' &&
3746
3784
typeof originalError . then === 'function' )
0 commit comments