@samitouri / QOS-React / commits / c11b196ae3

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 };