Try not. Do... or do not. Hydrate Suspense Boundaries. (#32851)
Assertively claim a SuspenseInstance. We already know we're hydrating. If there's no match, it throws anyway. So there's no other code path.
Sebastian Markbåge committed
Apr 11, 2025 at 10:52 UTC
961b625ab5d180180e836e0c7b221789f0ee336b
2 files changed
+22
-30
packages/react-reconciler/src/ReactFiberBeginWork.js
+9
-20
@@ -203,7 +203,6 @@ import {
203
pushFallbackTreeSuspenseHandler,
204
pushOffscreenSuspenseHandler,
205
reuseSuspenseHandlerOnStack,
206
- popSuspenseHandler,
206
} from './ReactFiberSuspenseContext';
207
import {
208
pushHiddenContext,
@@ -245,7 +244,7 @@ import {
244
claimHydratableSingleton,
245
tryToClaimNextHydratableInstance,
246
tryToClaimNextHydratableTextInstance,
248
- tryToClaimNextHydratableSuspenseInstance,
247
+ claimNextHydratableSuspenseInstance,
248
warnIfHydrating,
249
queueHydrationError,
250
} from './ReactFiberHydrationContext';
@@ -2151,24 +2150,14 @@ function updateSuspenseComponent(
2150
} else {
2151
pushFallbackTreeSuspenseHandler(workInProgress);
2152
}
2154
- tryToClaimNextHydratableSuspenseInstance(workInProgress);
2155
- // This could've been a dehydrated suspense component.
2156
- const suspenseState: null | SuspenseState = workInProgress.memoizedState;
2157
- if (suspenseState !== null) {
2158
- const dehydrated = suspenseState.dehydrated;
2159
- if (dehydrated !== null) {
2160
- return mountDehydratedSuspenseComponent(
2161
- workInProgress,
2162
- dehydrated,
2163
- renderLanes,
2164
- );
2165
- }
2166
- }
2167
- // If hydration didn't succeed, fall through to the normal Suspense path.
2168
- // To avoid a stack mismatch we need to pop the Suspense handler that we
2169
- // pushed above. This will become less awkward when move the hydration
2170
- // logic to its own fiber.
2171
- popSuspenseHandler(workInProgress);
2153
+ // This throws if we fail to hydrate.
2154
+ const dehydrated: SuspenseInstance =
2155
+ claimNextHydratableSuspenseInstance(workInProgress);
2156
+ return mountDehydratedSuspenseComponent(
2157
+ workInProgress,
2158
+ dehydrated,
2159
+ renderLanes,
2160
+ );
2161
}
2162
2163
const nextPrimaryChildren = nextProps.children;
packages/react-reconciler/src/ReactFiberHydrationContext.js
+13
-10
@@ -272,7 +272,10 @@ function tryHydrateText(fiber: Fiber, nextInstance: any) {
272
return false;
273
}
274
275
-function tryHydrateSuspense(fiber: Fiber, nextInstance: any) {
275
+function tryHydrateSuspense(
276
+ fiber: Fiber,
277
+ nextInstance: any,
278
+): null | SuspenseInstance {
279
// fiber is a SuspenseComponent Fiber
280
const suspenseInstance = canHydrateSuspenseInstance(
281
nextInstance,
@@ -298,9 +301,8 @@ function tryHydrateSuspense(fiber: Fiber, nextInstance: any) {
301
// While a Suspense Instance does have children, we won't step into
302
// it during the first pass. Instead, we'll reenter it later.
303
nextHydratableInstance = null;
301
- return true;
304
}
303
- return false;
305
+ return suspenseInstance;
306
}
307
308
export const HydrationMismatchException: mixed = new Error(
@@ -423,15 +425,16 @@ function tryToClaimNextHydratableTextInstance(fiber: Fiber): void {
425
}
426
}
427
426
-function tryToClaimNextHydratableSuspenseInstance(fiber: Fiber): void {
427
- if (!isHydrating) {
428
- return;
429
- }
428
+function claimNextHydratableSuspenseInstance(fiber: Fiber): SuspenseInstance {
429
const nextInstance = nextHydratableInstance;
431
- if (!nextInstance || !tryHydrateSuspense(fiber, nextInstance)) {
430
+ const suspenseInstance = nextInstance
431
+ ? tryHydrateSuspense(fiber, nextInstance)
432
+ : null;
433
+ if (suspenseInstance === null) {
434
warnNonHydratedInstance(fiber, nextInstance);
433
- throwOnHydrationMismatch(fiber);
435
+ throw throwOnHydrationMismatch(fiber);
436
}
437
+ return suspenseInstance;
438
}
439
440
export function tryToClaimNextHydratableFormMarkerInstance(
@@ -790,7 +793,7 @@ export {
793
claimHydratableSingleton,
794
tryToClaimNextHydratableInstance,
795
tryToClaimNextHydratableTextInstance,
793
- tryToClaimNextHydratableSuspenseInstance,
796
+ claimNextHydratableSuspenseInstance,
797
prepareToHydrateHostInstance,
798
prepareToHydrateHostTextInstance,
799
prepareToHydrateHostSuspenseInstance,