Move tail hydration mismatch back to hydration context (#28501)
In #23176 we added a special case in completeWork for SuspenseBoundaries if they still have trailing children. However, that misses a case because it doesn't log a recoverable error for the hydration mismatch. So we get an error that we rerendered. I think this special case was done to avoid contexts getting out of sync. I don't know why we didn't just move where the pop happens though so that's what I did here and let the regular pass throw instead. Seems to be pass the tests.
Sebastian Markbåge committed
Mar 5, 2024 at 20:54 UTC
c11b196ae3e2e3c5d143d9102b35a6b6fa97c849
3 files changed
+5
-21
packages/react-dom/src/__tests__/ReactDOMServerPartialHydration-test.internal.js
+1
@@ -538,6 +538,7 @@ describe('ReactDOMServerPartialHydration', () => {
538
assertLog([
539
'Server rendered',
540
'Client rendered',
541
+ 'Hydration failed because the initial UI does not match what was rendered on the server.',
542
'There was an error while hydrating this Suspense boundary. ' +
543
'Switched to client rendering.',
544
]);
packages/react-reconciler/src/ReactFiberCompleteWork.js
+4
-15
@@ -141,11 +141,9 @@ import {
141
prepareToHydrateHostInstance,
142
prepareToHydrateHostTextInstance,
143
prepareToHydrateHostSuspenseInstance,
144
- warnIfUnhydratedTailNodes,
144
popHydrationState,
145
resetHydrationState,
146
getIsHydrating,
148
- hasUnhydratedTailNodes,
147
upgradeHydrationErrorsToRecoverable,
148
} from './ReactFiberHydrationContext';
149
import {
@@ -866,18 +864,6 @@ function completeDehydratedSuspenseBoundary(
864
workInProgress: Fiber,
865
nextState: SuspenseState | null,
866
): boolean {
869
- if (
870
- hasUnhydratedTailNodes() &&
871
- (workInProgress.mode & ConcurrentMode) !== NoMode &&
872
- (workInProgress.flags & DidCapture) === NoFlags
873
- ) {
874
- warnIfUnhydratedTailNodes(workInProgress);
875
- resetHydrationState();
876
- workInProgress.flags |= ForceClientRender | DidCapture;
877
-
878
- return false;
879
- }
880
-
867
const wasHydrated = popHydrationState(workInProgress);
868
869
if (nextState !== null && nextState.dehydrated !== null) {
@@ -1337,7 +1323,6 @@ function completeWork(
1323
return null;
1324
}
1325
case SuspenseComponent: {
1340
- popSuspenseHandler(workInProgress);
1326
const nextState: null | SuspenseState = workInProgress.memoizedState;
1327
1328
// Special path for dehydrated boundaries. We may eventually move this
@@ -1358,10 +1343,12 @@ function completeWork(
1343
);
1344
if (!fallthroughToNormalSuspensePath) {
1345
if (workInProgress.flags & ForceClientRender) {
1346
+ popSuspenseHandler(workInProgress);
1347
// Special case. There were remaining unhydrated nodes. We treat
1348
// this as a mismatch. Revert to client rendering.
1349
return workInProgress;
1350
} else {
1351
+ popSuspenseHandler(workInProgress);
1352
// Did not finish hydrating, either because this is the initial
1353
// render or because something suspended.
1354
return null;
@@ -1371,6 +1358,8 @@ function completeWork(
1358
// Continue with the normal Suspense path.
1359
}
1360
1361
+ popSuspenseHandler(workInProgress);
1362
+
1363
if ((workInProgress.flags & DidCapture) !== NoFlags) {
1364
// Something suspended. Re-render with the fallback children.
1365
workInProgress.lanes = renderLanes;
packages/react-reconciler/src/ReactFiberHydrationContext.js
-6
@@ -893,10 +893,6 @@ function popHydrationState(fiber: Fiber): boolean {
893
return true;
894
}
895
896
-function hasUnhydratedTailNodes(): boolean {
897
- return isHydrating && nextHydratableInstance !== null;
898
-}
899
-
896
function warnIfUnhydratedTailNodes(fiber: Fiber) {
897
let nextInstance = nextHydratableInstance;
898
while (nextInstance) {
@@ -952,6 +948,4 @@ export {
948
prepareToHydrateHostTextInstance,
949
prepareToHydrateHostSuspenseInstance,
950
popHydrationState,
955
- hasUnhydratedTailNodes,
956
- warnIfUnhydratedTailNodes,
951
};