diff --git a/packages/react-dom/src/__tests__/ReactDOMServerSelectiveHydration-test.internal.js b/packages/react-dom/src/__tests__/ReactDOMServerSelectiveHydration-test.internal.js index a8f06a3227b16..3469c4faf0683 100644 --- a/packages/react-dom/src/__tests__/ReactDOMServerSelectiveHydration-test.internal.js +++ b/packages/react-dom/src/__tests__/ReactDOMServerSelectiveHydration-test.internal.js @@ -1496,10 +1496,12 @@ describe('ReactDOMServerSelectiveHydration', () => { // Start rendering. This will force the first boundary to hydrate // by scheduling it at one higher pri than Idle. expect(Scheduler).toFlushAndYieldThrough([ + // An update was scheduled to force hydrate the boundary, but React will + // continue rendering at Idle until the next time React yields. This is + // fine though because it will switch to the hydration level when it + // re-enters the work loop. 'App', - - // Start hydrating A - 'A', + 'AA', ]); // Hover over A which (could) schedule at one higher pri than Idle. diff --git a/packages/react-reconciler/src/ReactFiberBeginWork.js b/packages/react-reconciler/src/ReactFiberBeginWork.js index bde65936f1c01..78372d1762916 100644 --- a/packages/react-reconciler/src/ReactFiberBeginWork.js +++ b/packages/react-reconciler/src/ReactFiberBeginWork.js @@ -282,14 +282,6 @@ import { const ReactCurrentOwner = ReactSharedInternals.ReactCurrentOwner; -// A special exception that's used to unwind the stack when an update flows -// into a dehydrated boundary. -export const SelectiveHydrationException: mixed = new Error( - "This is not a real error. It's an implementation detail of React's " + - "selective hydration feature. If this leaks into userspace, it's a bug in " + - 'React. Please file an issue.', -); - let didReceiveUpdate: boolean = false; let didWarnAboutBadClass; @@ -2868,16 +2860,6 @@ function updateDehydratedSuspenseComponent( attemptHydrationAtLane, eventTime, ); - - // Throw a special object that signals to the work loop that it should - // interrupt the current render. - // - // Because we're inside a React-only execution stack, we don't - // strictly need to throw here — we could instead modify some internal - // work loop state. But using an exception means we don't need to - // check for this case on every iteration of the work loop. So doing - // it this way moves the check out of the fast path. - throw SelectiveHydrationException; } else { // We have already tried to ping at a higher priority than we're rendering with // so if we got here, we must have failed to hydrate at those levels. We must @@ -2888,17 +2870,15 @@ function updateDehydratedSuspenseComponent( } } - // If we did not selectively hydrate, we'll continue rendering without - // hydrating. Mark this tree as suspended to prevent it from committing - // outside a transition. - // - // This path should only happen if the hydration lane already suspended. - // Currently, it also happens during sync updates because there is no - // hydration lane for sync updates. + // If we have scheduled higher pri work above, this will just abort the render + // since we now have higher priority work. We'll try to infinitely suspend until + // we yield. TODO: We could probably just force yielding earlier instead. + renderDidSuspendDelayIfPossible(); + // If we rendered synchronously, we won't yield so have to render something. + // This will cause us to delete any existing content. // TODO: We should ideally have a sync hydration lane that we can apply to do // a pass where we hydrate this subtree in place using the previous Context and then // reapply the update afterwards. - renderDidSuspendDelayIfPossible(); return retrySuspenseComponentWithoutHydrating( current, workInProgress, diff --git a/packages/react-reconciler/src/ReactFiberWorkLoop.js b/packages/react-reconciler/src/ReactFiberWorkLoop.js index 6f86c38a9bc20..0c86cbb324020 100644 --- a/packages/react-reconciler/src/ReactFiberWorkLoop.js +++ b/packages/react-reconciler/src/ReactFiberWorkLoop.js @@ -175,7 +175,6 @@ import { } from './ReactEventPriorities'; import {requestCurrentTransition, NoTransition} from './ReactFiberTransition'; import { - SelectiveHydrationException, beginWork as originalBeginWork, replayFunctionComponent, } from './ReactFiberBeginWork'; @@ -317,14 +316,13 @@ let workInProgress: Fiber | null = null; // The lanes we're rendering let workInProgressRootRenderLanes: Lanes = NoLanes; -opaque type SuspendedReason = 0 | 1 | 2 | 3 | 4 | 5 | 6; +opaque type SuspendedReason = 0 | 1 | 2 | 3 | 4 | 5; const NotSuspended: SuspendedReason = 0; const SuspendedOnError: SuspendedReason = 1; const SuspendedOnData: SuspendedReason = 2; const SuspendedOnImmediate: SuspendedReason = 3; const SuspendedOnDeprecatedThrowPromise: SuspendedReason = 4; const SuspendedAndReadyToUnwind: SuspendedReason = 5; -const SuspendedOnHydration: SuspendedReason = 6; // When this is true, the work-in-progress fiber just suspended (or errored) and // we've yet to unwind the stack. In some cases, we may yield to the main thread @@ -1799,17 +1797,6 @@ function handleThrow(root, thrownValue): void { workInProgressSuspendedReason = shouldAttemptToSuspendUntilDataResolves() ? SuspendedOnData : SuspendedOnImmediate; - } else if (thrownValue === SelectiveHydrationException) { - // An update flowed into a dehydrated boundary. Before we can apply the - // update, we need to finish hydrating. Interrupt the work-in-progress - // render so we can restart at the hydration lane. - // - // The ideal implementation would be able to switch contexts without - // unwinding the current stack. - // - // We could name this something more general but as of now it's the only - // case where we think this should happen. - workInProgressSuspendedReason = SuspendedOnHydration; } else { // This is a regular error. const isWakeable = @@ -2051,7 +2038,7 @@ function renderRootSync(root: FiberRoot, lanes: Lanes) { markRenderStarted(lanes); } - outer: do { + do { try { if ( workInProgressSuspendedReason !== NotSuspended && @@ -2067,23 +2054,11 @@ function renderRootSync(root: FiberRoot, lanes: Lanes) { // function and fork the behavior some other way. const unitOfWork = workInProgress; const thrownValue = workInProgressThrownValue; - switch (workInProgressSuspendedReason) { - case SuspendedOnHydration: { - // Selective hydration. An update flowed into a dehydrated tree. - // Interrupt the current render so the work loop can switch to the - // hydration lane. - workInProgress = null; - workInProgressRootExitStatus = RootDidNotComplete; - break outer; - } - default: { - // Continue with the normal work loop. - workInProgressSuspendedReason = NotSuspended; - workInProgressThrownValue = null; - unwindSuspendedUnitOfWork(unitOfWork, thrownValue); - break; - } - } + workInProgressSuspendedReason = NotSuspended; + workInProgressThrownValue = null; + unwindSuspendedUnitOfWork(unitOfWork, thrownValue); + + // Continue with the normal work loop. } workLoopSync(); break; @@ -2241,14 +2216,6 @@ function renderRootConcurrent(root: FiberRoot, lanes: Lanes) { unwindSuspendedUnitOfWork(unitOfWork, thrownValue); break; } - case SuspendedOnHydration: { - // Selective hydration. An update flowed into a dehydrated tree. - // Interrupt the current render so the work loop can switch to the - // hydration lane. - workInProgress = null; - workInProgressRootExitStatus = RootDidNotComplete; - break outer; - } default: { throw new Error( 'Unexpected SuspendedReason. This is a bug in React.', @@ -3774,7 +3741,6 @@ if (__DEV__ && replayFailedUnitOfWorkWithInvokeGuardedCallback) { if ( didSuspendOrErrorWhileHydratingDEV() || originalError === SuspenseException || - originalError === SelectiveHydrationException || (originalError !== null && typeof originalError === 'object' && typeof originalError.then === 'function')