Repository: facebook/react
Pull request: [Fiber] Schedule client renders using non-hydration lane

Related to #31752.

When hydrating, we have two different ways of handling a Suspense boundary that the server has already given up on and decided to client render. If we have already hydrated the parent and then later this happens, then we'll use the retry lane like any ping. If we discover that it was already in client-render mode when we discover the Suspense boundary for the first time, then schedule a default lane to let us first finish the current render and then upgrade the priority to sync to try to client render this boundary as soon as possible since we're holding back content.

We used to use the `DefaultHydrationLane` for this but this is not really a Hydration. It's actually a client render. If we get any other updates flowing in from above at the same time we might as well do them in the same pass instead of two passes. So this should be considered more like any update.

This also means that visually the client render pass now gets painted as a render instead of a hydration.

This show the flow of a shell being hydrated at the default priority, then a Suspense boundary being discovered and hydrated at Idle and then an inner boundary being discovered as client rendered which gets upgraded to default.

<img width="1363" alt="Screenshot 2024-12-14 at 12 13 57 AM" src="https://github.com/user-attachments/assets/a141133e-4856-4f38-a11f-f26bd00b6245" />


Changed files:
filename: packages/react-reconciler/src/ReactFiberBeginWork.js
patch: @@ -111,6 +111,7 @@ import {
   disableLegacyMode,
   disableDefaultPropsExceptForClasses,
   enableOwnerStacks,
+  enableHydrationLaneScheduling,
 } from 'shared/ReactFeatureFlags';
 import isArray from 'shared/isArray';
 import shallowEqual from 'shared/shallowEqual';
@@ -146,6 +147,7 @@ import {
   NoLane,
   NoLanes,
   OffscreenLane,
+  DefaultLane,
   DefaultHydrationLane,
   SomeRetryLane,
   includesSomeLane,
@@ -2646,14 +2648,10 @@ function mountDehydratedSuspenseComponent(
     // have timed out. In theory we could render it in this pass but it would have the
     // wrong priority associated with it and will prevent hydration of parent path.
     // Instead, we'll leave work left on it to render it in a separate commit.
-
-    // TODO This time should be the time at which the server rendered response that is
-    // a parent to this boundary was displayed. However, since we currently don't have
-    // a protocol to transfer that time, we'll just estimate it by using the current
-    // time. This will mean that Suspense timeouts are slightly shifted to later than
-    // they should be.
     // Schedule a normal pri update to render this content.
-    workInProgress.lanes = laneToLanes(DefaultHydrationLane);
+    workInProgress.lanes = laneToLanes(
+      enableHydrationLaneScheduling ? DefaultLane : DefaultHydrationLane,
+    );
   } else {
     // We'll continue hydrating the rest at offscreen priority since we'll already
     // be showing the right content coming from the server, it is no rush.

filename: packages/shared/ReactFeatureFlags.js
patch: @@ -22,6 +22,8 @@
 // when it rolls out to prod. We should remove these as soon as possible.
 // -----------------------------------------------------------------------------
 
+export const enableHydrationLaneScheduling = true;
+
 // -----------------------------------------------------------------------------
 // Land or remove (moderate effort)
 //
@@ -111,8 +113,6 @@ export const enableSuspenseAvoidThisFallback = false;
 
 export const enableCPUSuspense = __EXPERIMENTAL__;
 
-export const enableHydrationLaneScheduling = true;
-
 // Test this at Meta before enabling.
 export const enableNoCloningMemoCache = false;