diff --git a/packages/react-dom/src/__tests__/ReactDOMServerPartialHydration-test.internal.js b/packages/react-dom/src/__tests__/ReactDOMServerPartialHydration-test.internal.js index 7d98dbd87dc78..8875fa4c0aaea 100644 --- a/packages/react-dom/src/__tests__/ReactDOMServerPartialHydration-test.internal.js +++ b/packages/react-dom/src/__tests__/ReactDOMServerPartialHydration-test.internal.js @@ -19,6 +19,7 @@ let ReactFeatureFlags; let Suspense; let SuspenseList; let Offscreen; +let useSyncExternalStore; let act; let IdleEventPriority; let waitForAll; @@ -113,6 +114,7 @@ describe('ReactDOMServerPartialHydration', () => { Scheduler = require('scheduler'); Suspense = React.Suspense; Offscreen = React.unstable_Offscreen; + useSyncExternalStore = React.useSyncExternalStore; if (gate(flags => flags.enableSuspenseList)) { SuspenseList = React.SuspenseList; } @@ -480,6 +482,26 @@ describe('ReactDOMServerPartialHydration', () => { }); it('recovers with client render when server rendered additional nodes at suspense root', async () => { + function CheckIfHydrating({children}) { + // This is a trick to check whether we're hydrating or not, since React + // doesn't expose that information currently except + // via useSyncExternalStore. + let serverOrClient = '(unknown)'; + useSyncExternalStore( + () => {}, + () => { + serverOrClient = 'Client rendered'; + return null; + }, + () => { + serverOrClient = 'Server rendered'; + return null; + }, + ); + Scheduler.log(serverOrClient); + return null; + } + const ref = React.createRef(); function App({hasB}) { return ( @@ -487,6 +509,7 @@ describe('ReactDOMServerPartialHydration', () => { A {hasB ? B : null} +
Sibling
@@ -494,6 +517,7 @@ describe('ReactDOMServerPartialHydration', () => { } const finalHTML = ReactDOMServer.renderToString(); + assertLog(['Server rendered']); const container = document.createElement('div'); container.innerHTML = finalHTML; @@ -514,12 +538,12 @@ describe('ReactDOMServerPartialHydration', () => { }); }).toErrorDev('Did not expect server HTML to contain a in
'); - jest.runAllTimers(); - expect(container.innerHTML).toContain('A'); expect(container.innerHTML).not.toContain('B'); assertLog([ + 'Server rendered', + 'Client rendered', 'There was an error while hydrating this Suspense boundary. ' + 'Switched to client rendering.', ]); diff --git a/packages/react-reconciler/src/ReactFiberCompleteWork.js b/packages/react-reconciler/src/ReactFiberCompleteWork.js index 73a9a455a617f..d67c78103ff00 100644 --- a/packages/react-reconciler/src/ReactFiberCompleteWork.js +++ b/packages/react-reconciler/src/ReactFiberCompleteWork.js @@ -85,8 +85,6 @@ import { StaticMask, MutationMask, Passive, - Incomplete, - ShouldCapture, ForceClientRender, SuspenseyCommit, ScheduleRetry, @@ -839,7 +837,7 @@ function completeDehydratedSuspenseBoundary( ) { warnIfUnhydratedTailNodes(workInProgress); resetHydrationState(); - workInProgress.flags |= ForceClientRender | Incomplete | ShouldCapture; + workInProgress.flags |= ForceClientRender | DidCapture; return false; } @@ -1284,7 +1282,7 @@ function completeWork( nextState, ); if (!fallthroughToNormalSuspensePath) { - if (workInProgress.flags & ShouldCapture) { + if (workInProgress.flags & ForceClientRender) { // Special case. There were remaining unhydrated nodes. We treat // this as a mismatch. Revert to client rendering. return workInProgress;