From a6a05e0f3b8775ca6abd995f2802500e1945adcf Mon Sep 17 00:00:00 2001 From: Marco Salazar Date: Mon, 31 Jan 2022 14:55:53 -0500 Subject: [PATCH 1/5] rename --- ...DOMServerPartialHydration-test.internal.js | 121 +++++++++++++++++- .../src/ReactFiberCompleteWork.new.js | 22 +++- .../src/ReactFiberCompleteWork.old.js | 21 ++- .../src/ReactFiberHydrationContext.new.js | 42 +++++- .../src/ReactFiberHydrationContext.old.js | 42 +++++- 5 files changed, 228 insertions(+), 20 deletions(-) diff --git a/packages/react-dom/src/__tests__/ReactDOMServerPartialHydration-test.internal.js b/packages/react-dom/src/__tests__/ReactDOMServerPartialHydration-test.internal.js index 1c65501f7b382..8f2913c15d600 100644 --- a/packages/react-dom/src/__tests__/ReactDOMServerPartialHydration-test.internal.js +++ b/packages/react-dom/src/__tests__/ReactDOMServerPartialHydration-test.internal.js @@ -437,7 +437,7 @@ describe('ReactDOMServerPartialHydration', () => { expect(container.innerHTML).toContain('
Sibling
'); }); - it('recovers when server rendered additional nodes', async () => { + it('recovers with client render when server rendered additional nodes at suspense root', async () => { const ref = React.createRef(); function App({hasB}) { return ( @@ -462,15 +462,128 @@ describe('ReactDOMServerPartialHydration', () => { expect(container.innerHTML).toContain('B'); expect(ref.current).toBe(null); - ReactDOM.hydrateRoot(container, ); expect(() => { - Scheduler.unstable_flushAll(); + act(() => { + ReactDOM.hydrateRoot(container, ); + }); }).toErrorDev('Did not expect server HTML to contain a in
'); + jest.runAllTimers(); expect(container.innerHTML).toContain('A'); expect(container.innerHTML).not.toContain('B'); - expect(ref.current).toBe(span); + + if (gate(flags => flags.enableClientRenderFallbackOnHydrationMismatch)) { + expect(ref.current).not.toBe(span); + } else { + expect(ref.current).toBe(span); + } + }); + + it('recovers with client render when server rendered additional nodes at suspense root after unsuspending', async () => { + spyOnDev(console, 'error'); + const ref = React.createRef(); + function App({hasB}) { + return ( +
+ + + A + {hasB ? B : null} + +
Sibling
+
+ ); + } + + let shouldSuspend = false; + let resolve; + const promise = new Promise(res => { + resolve = () => { + shouldSuspend = false; + res(); + }; + }); + function Suspender() { + if (shouldSuspend) { + throw promise; + } + return <>; + } + + const finalHTML = ReactDOMServer.renderToString(); + + const container = document.createElement('div'); + container.innerHTML = finalHTML; + + const span = container.getElementsByTagName('span')[0]; + + expect(container.innerHTML).toContain('A'); + expect(container.innerHTML).toContain('B'); + expect(ref.current).toBe(null); + + shouldSuspend = true; + act(() => { + ReactDOM.hydrateRoot(container, ); + }); + + // await expect(async () => { + resolve(); + await promise; + Scheduler.unstable_flushAll(); + await null; + jest.runAllTimers(); + // }).toErrorDev('Did not expect server HTML to contain a in
'); + + expect(container.innerHTML).toContain('A'); + expect(container.innerHTML).not.toContain('B'); + if (gate(flags => flags.enableClientRenderFallbackOnHydrationMismatch)) { + expect(ref.current).not.toBe(span); + } else { + expect(ref.current).toBe(span); + } + }); + + it('recovers with client render when server rendered additional nodes deep inside suspense root', async () => { + const ref = React.createRef(); + function App({hasB}) { + return ( +
+ +
+ A + {hasB ? B : null} +
+
+
Sibling
+
+ ); + } + + const finalHTML = ReactDOMServer.renderToString(); + + const container = document.createElement('div'); + container.innerHTML = finalHTML; + + const span = container.getElementsByTagName('span')[0]; + + expect(container.innerHTML).toContain('A'); + expect(container.innerHTML).toContain('B'); + expect(ref.current).toBe(null); + + expect(() => { + act(() => { + ReactDOM.hydrateRoot(container, ); + }); + }).toErrorDev('Did not expect server HTML to contain a in
'); + + expect(container.innerHTML).toContain('A'); + expect(container.innerHTML).not.toContain('B'); + if (gate(flags => flags.enableClientRenderFallbackOnHydrationMismatch)) { + expect(ref.current).not.toBe(span); + } else { + expect(ref.current).toBe(span); + } }); it('calls the onDeleted hydration callback if the parent gets deleted', async () => { diff --git a/packages/react-reconciler/src/ReactFiberCompleteWork.new.js b/packages/react-reconciler/src/ReactFiberCompleteWork.new.js index 91c564dd465ba..dd4bde63336e6 100644 --- a/packages/react-reconciler/src/ReactFiberCompleteWork.new.js +++ b/packages/react-reconciler/src/ReactFiberCompleteWork.new.js @@ -29,7 +29,10 @@ import type { import type {SuspenseContext} from './ReactFiberSuspenseContext.new'; import type {OffscreenState} from './ReactFiberOffscreenComponent'; import type {Cache, SpawnedCachePool} from './ReactFiberCacheComponent.new'; -import {enableSuspenseAvoidThisFallback} from 'shared/ReactFeatureFlags'; +import { + enableClientRenderFallbackOnHydrationMismatch, + enableSuspenseAvoidThisFallback, +} from 'shared/ReactFeatureFlags'; import {resetWorkInProgressVersions as resetMutableSourceWorkInProgressVersions} from './ReactMutableSource.new'; @@ -74,6 +77,9 @@ import { StaticMask, MutationMask, Passive, + Incomplete, + ShouldCapture, + ForceClientRender, } from './ReactFiberFlags'; import { @@ -120,9 +126,11 @@ import { prepareToHydrateHostInstance, prepareToHydrateHostTextInstance, prepareToHydrateHostSuspenseInstance, + warnDeleteNextHydratableInstance, popHydrationState, resetHydrationState, getIsHydrating, + hasUnhydratedTailNodes, } from './ReactFiberHydrationContext.new'; import { enableSuspenseCallback, @@ -1021,6 +1029,18 @@ function completeWork( const nextState: null | SuspenseState = workInProgress.memoizedState; if (enableSuspenseServerRenderer) { + if ( + enableClientRenderFallbackOnHydrationMismatch && + hasUnhydratedTailNodes() && + (workInProgress.mode & ConcurrentMode) !== NoMode && + (workInProgress.flags & DidCapture) === NoFlags + ) { + warnDeleteNextHydratableInstance(workInProgress); + resetHydrationState(); + workInProgress.flags |= + ForceClientRender | Incomplete | ShouldCapture; + return workInProgress; + } if (nextState !== null && nextState.dehydrated !== null) { // We might be inside a hydration state the first time we're picking up this // Suspense boundary, and also after we've reentered it for further hydration. diff --git a/packages/react-reconciler/src/ReactFiberCompleteWork.old.js b/packages/react-reconciler/src/ReactFiberCompleteWork.old.js index ea994583cfe8d..63b51efbbb9b8 100644 --- a/packages/react-reconciler/src/ReactFiberCompleteWork.old.js +++ b/packages/react-reconciler/src/ReactFiberCompleteWork.old.js @@ -29,7 +29,10 @@ import type { import type {SuspenseContext} from './ReactFiberSuspenseContext.old'; import type {OffscreenState} from './ReactFiberOffscreenComponent'; import type {Cache, SpawnedCachePool} from './ReactFiberCacheComponent.old'; -import {enableSuspenseAvoidThisFallback} from 'shared/ReactFeatureFlags'; +import { + enableClientRenderFallbackOnHydrationMismatch, + enableSuspenseAvoidThisFallback, +} from 'shared/ReactFeatureFlags'; import {resetWorkInProgressVersions as resetMutableSourceWorkInProgressVersions} from './ReactMutableSource.old'; @@ -74,6 +77,9 @@ import { StaticMask, MutationMask, Passive, + Incomplete, + ShouldCapture, + ForceClientRender, } from './ReactFiberFlags'; import { @@ -120,9 +126,11 @@ import { prepareToHydrateHostInstance, prepareToHydrateHostTextInstance, prepareToHydrateHostSuspenseInstance, + warnDeleteNextHydratableInstance, popHydrationState, resetHydrationState, getIsHydrating, + hasUnhydratedTailNodes, } from './ReactFiberHydrationContext.old'; import { enableSuspenseCallback, @@ -1021,6 +1029,17 @@ function completeWork( const nextState: null | SuspenseState = workInProgress.memoizedState; if (enableSuspenseServerRenderer) { + if ( + enableClientRenderFallbackOnHydrationMismatch && + hasUnhydratedTailNodes() && + (workInProgress.flags & DidCapture) === NoFlags + ) { + warnDeleteNextHydratableInstance(workInProgress); + resetHydrationState(); + workInProgress.flags |= + ForceClientRender | Incomplete | ShouldCapture; + return workInProgress; + } if (nextState !== null && nextState.dehydrated !== null) { // We might be inside a hydration state the first time we're picking up this // Suspense boundary, and also after we've reentered it for further hydration. diff --git a/packages/react-reconciler/src/ReactFiberHydrationContext.new.js b/packages/react-reconciler/src/ReactFiberHydrationContext.new.js index 614c6c9d946ca..8989393936c9d 100644 --- a/packages/react-reconciler/src/ReactFiberHydrationContext.new.js +++ b/packages/react-reconciler/src/ReactFiberHydrationContext.new.js @@ -26,7 +26,13 @@ import { HostRoot, SuspenseComponent, } from './ReactWorkTags'; -import {ChildDeletion, Placement, Hydrating} from './ReactFiberFlags'; +import { + ChildDeletion, + Placement, + Hydrating, + NoFlags, + DidCapture, +} from './ReactFiberFlags'; import { createFiberFromHostInstanceForDeletion, @@ -121,7 +127,7 @@ function reenterHydrationStateFromDehydratedSuspenseInstance( return true; } -function deleteHydratableInstance( +function warnUnhydratedInstance( returnFiber: Fiber, instance: HydratableInstance, ) { @@ -151,7 +157,13 @@ function deleteHydratableInstance( break; } } +} +function deleteHydratableInstance( + returnFiber: Fiber, + instance: HydratableInstance, +) { + warnUnhydratedInstance(returnFiber, instance); const childToDelete = createFiberFromHostInstanceForDeletion(); childToDelete.stateNode = instance; childToDelete.return = returnFiber; @@ -330,7 +342,8 @@ function tryHydrate(fiber, nextInstance) { function throwOnHydrationMismatchIfConcurrentMode(fiber: Fiber) { if ( enableClientRenderFallbackOnHydrationMismatch && - (fiber.mode & ConcurrentMode) !== NoMode + (fiber.mode & ConcurrentMode) !== NoMode && + (fiber.flags & DidCapture) === NoFlags ) { throw new Error( 'An error occurred during hydration. The server HTML was replaced with client content', @@ -539,12 +552,15 @@ function popHydrationState(fiber: Fiber): boolean { !shouldSetTextContent(fiber.type, fiber.memoizedProps))) ) { let nextInstance = nextHydratableInstance; - while (nextInstance) { - deleteHydratableInstance(fiber, nextInstance); - nextInstance = getNextHydratableSibling(nextInstance); + if (nextInstance) { + warnDeleteNextHydratableInstance(fiber); + throwOnHydrationMismatchIfConcurrentMode(fiber); + while (nextInstance) { + deleteHydratableInstance(fiber, nextInstance); + nextInstance = getNextHydratableSibling(nextInstance); + } } } - popToNextHostParent(fiber); if (fiber.tag === SuspenseComponent) { nextHydratableInstance = skipPastDehydratedSuspenseInstance(fiber); @@ -556,6 +572,16 @@ function popHydrationState(fiber: Fiber): boolean { return true; } +function hasUnhydratedTailNodes() { + return isHydrating && nextHydratableInstance !== null; +} + +function warnDeleteNextHydratableInstance(fiber: Fiber) { + if (nextHydratableInstance) { + warnUnhydratedInstance(fiber, nextHydratableInstance); + } +} + function resetHydrationState(): void { if (!supportsHydration) { return; @@ -581,4 +607,6 @@ export { prepareToHydrateHostTextInstance, prepareToHydrateHostSuspenseInstance, popHydrationState, + hasMore, + warnDeleteNextHydratableInstance, }; diff --git a/packages/react-reconciler/src/ReactFiberHydrationContext.old.js b/packages/react-reconciler/src/ReactFiberHydrationContext.old.js index b7bca8217d979..cdd6a986b710a 100644 --- a/packages/react-reconciler/src/ReactFiberHydrationContext.old.js +++ b/packages/react-reconciler/src/ReactFiberHydrationContext.old.js @@ -26,7 +26,13 @@ import { HostRoot, SuspenseComponent, } from './ReactWorkTags'; -import {ChildDeletion, Placement, Hydrating} from './ReactFiberFlags'; +import { + ChildDeletion, + Placement, + Hydrating, + NoFlags, + DidCapture, +} from './ReactFiberFlags'; import { createFiberFromHostInstanceForDeletion, @@ -121,7 +127,7 @@ function reenterHydrationStateFromDehydratedSuspenseInstance( return true; } -function deleteHydratableInstance( +function warnUnhydratedInstance( returnFiber: Fiber, instance: HydratableInstance, ) { @@ -151,7 +157,13 @@ function deleteHydratableInstance( break; } } +} +function deleteHydratableInstance( + returnFiber: Fiber, + instance: HydratableInstance, +) { + warnUnhydratedInstance(returnFiber, instance); const childToDelete = createFiberFromHostInstanceForDeletion(); childToDelete.stateNode = instance; childToDelete.return = returnFiber; @@ -330,7 +342,8 @@ function tryHydrate(fiber, nextInstance) { function throwOnHydrationMismatchIfConcurrentMode(fiber: Fiber) { if ( enableClientRenderFallbackOnHydrationMismatch && - (fiber.mode & ConcurrentMode) !== NoMode + (fiber.mode & ConcurrentMode) !== NoMode && + (fiber.flags & DidCapture) === NoFlags ) { throw new Error( 'An error occurred during hydration. The server HTML was replaced with client content', @@ -539,12 +552,15 @@ function popHydrationState(fiber: Fiber): boolean { !shouldSetTextContent(fiber.type, fiber.memoizedProps))) ) { let nextInstance = nextHydratableInstance; - while (nextInstance) { - deleteHydratableInstance(fiber, nextInstance); - nextInstance = getNextHydratableSibling(nextInstance); + if (nextInstance) { + warnDeleteNextHydratableInstance(fiber); + throwOnHydrationMismatchIfConcurrentMode(fiber); + while (nextInstance) { + deleteHydratableInstance(fiber, nextInstance); + nextInstance = getNextHydratableSibling(nextInstance); + } } } - popToNextHostParent(fiber); if (fiber.tag === SuspenseComponent) { nextHydratableInstance = skipPastDehydratedSuspenseInstance(fiber); @@ -556,6 +572,16 @@ function popHydrationState(fiber: Fiber): boolean { return true; } +function hasUnhydratedTailNodes() { + return isHydrating && nextHydratableInstance !== null; +} + +function warnDeleteNextHydratableInstance(fiber: Fiber) { + if (nextHydratableInstance) { + warnUnhydratedInstance(fiber, nextHydratableInstance); + } +} + function resetHydrationState(): void { if (!supportsHydration) { return; @@ -581,4 +607,6 @@ export { prepareToHydrateHostTextInstance, prepareToHydrateHostSuspenseInstance, popHydrationState, + hasUnhydratedTailNodes, + warnDeleteNextHydratableInstance, }; From a902b50c066254e3c3f4ddfe942a9e01dd0285fb Mon Sep 17 00:00:00 2001 From: Marco Salazar Date: Mon, 31 Jan 2022 15:08:40 -0500 Subject: [PATCH 2/5] rename --- .../react-reconciler/src/ReactFiberCompleteWork.new.js | 4 ++-- .../react-reconciler/src/ReactFiberCompleteWork.old.js | 4 ++-- .../src/ReactFiberHydrationContext.new.js | 8 ++++---- .../src/ReactFiberHydrationContext.old.js | 6 +++--- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/packages/react-reconciler/src/ReactFiberCompleteWork.new.js b/packages/react-reconciler/src/ReactFiberCompleteWork.new.js index dd4bde63336e6..5e8ee6d6abe13 100644 --- a/packages/react-reconciler/src/ReactFiberCompleteWork.new.js +++ b/packages/react-reconciler/src/ReactFiberCompleteWork.new.js @@ -126,7 +126,7 @@ import { prepareToHydrateHostInstance, prepareToHydrateHostTextInstance, prepareToHydrateHostSuspenseInstance, - warnDeleteNextHydratableInstance, + warnUnhydratedNextInstance, popHydrationState, resetHydrationState, getIsHydrating, @@ -1035,7 +1035,7 @@ function completeWork( (workInProgress.mode & ConcurrentMode) !== NoMode && (workInProgress.flags & DidCapture) === NoFlags ) { - warnDeleteNextHydratableInstance(workInProgress); + warnUnhydratedNextInstance(workInProgress); resetHydrationState(); workInProgress.flags |= ForceClientRender | Incomplete | ShouldCapture; diff --git a/packages/react-reconciler/src/ReactFiberCompleteWork.old.js b/packages/react-reconciler/src/ReactFiberCompleteWork.old.js index 63b51efbbb9b8..18ce7faa141cd 100644 --- a/packages/react-reconciler/src/ReactFiberCompleteWork.old.js +++ b/packages/react-reconciler/src/ReactFiberCompleteWork.old.js @@ -126,7 +126,7 @@ import { prepareToHydrateHostInstance, prepareToHydrateHostTextInstance, prepareToHydrateHostSuspenseInstance, - warnDeleteNextHydratableInstance, + warnUnhydratedNextInstance, popHydrationState, resetHydrationState, getIsHydrating, @@ -1034,7 +1034,7 @@ function completeWork( hasUnhydratedTailNodes() && (workInProgress.flags & DidCapture) === NoFlags ) { - warnDeleteNextHydratableInstance(workInProgress); + warnUnhydratedNextInstance(workInProgress); resetHydrationState(); workInProgress.flags |= ForceClientRender | Incomplete | ShouldCapture; diff --git a/packages/react-reconciler/src/ReactFiberHydrationContext.new.js b/packages/react-reconciler/src/ReactFiberHydrationContext.new.js index 8989393936c9d..e89d2c5ce0fbf 100644 --- a/packages/react-reconciler/src/ReactFiberHydrationContext.new.js +++ b/packages/react-reconciler/src/ReactFiberHydrationContext.new.js @@ -553,7 +553,7 @@ function popHydrationState(fiber: Fiber): boolean { ) { let nextInstance = nextHydratableInstance; if (nextInstance) { - warnDeleteNextHydratableInstance(fiber); + warnUnhydratedNextInstance(fiber); throwOnHydrationMismatchIfConcurrentMode(fiber); while (nextInstance) { deleteHydratableInstance(fiber, nextInstance); @@ -576,7 +576,7 @@ function hasUnhydratedTailNodes() { return isHydrating && nextHydratableInstance !== null; } -function warnDeleteNextHydratableInstance(fiber: Fiber) { +function warnUnhydratedNextInstance(fiber: Fiber) { if (nextHydratableInstance) { warnUnhydratedInstance(fiber, nextHydratableInstance); } @@ -607,6 +607,6 @@ export { prepareToHydrateHostTextInstance, prepareToHydrateHostSuspenseInstance, popHydrationState, - hasMore, - warnDeleteNextHydratableInstance, + hasUnhydratedTailNodes, + warnUnhydratedNextInstance, }; diff --git a/packages/react-reconciler/src/ReactFiberHydrationContext.old.js b/packages/react-reconciler/src/ReactFiberHydrationContext.old.js index cdd6a986b710a..df027226e6444 100644 --- a/packages/react-reconciler/src/ReactFiberHydrationContext.old.js +++ b/packages/react-reconciler/src/ReactFiberHydrationContext.old.js @@ -553,7 +553,7 @@ function popHydrationState(fiber: Fiber): boolean { ) { let nextInstance = nextHydratableInstance; if (nextInstance) { - warnDeleteNextHydratableInstance(fiber); + warnUnhydratedNextInstance(fiber); throwOnHydrationMismatchIfConcurrentMode(fiber); while (nextInstance) { deleteHydratableInstance(fiber, nextInstance); @@ -576,7 +576,7 @@ function hasUnhydratedTailNodes() { return isHydrating && nextHydratableInstance !== null; } -function warnDeleteNextHydratableInstance(fiber: Fiber) { +function warnUnhydratedNextInstance(fiber: Fiber) { if (nextHydratableInstance) { warnUnhydratedInstance(fiber, nextHydratableInstance); } @@ -608,5 +608,5 @@ export { prepareToHydrateHostSuspenseInstance, popHydrationState, hasUnhydratedTailNodes, - warnDeleteNextHydratableInstance, + warnUnhydratedNextInstance, }; From f8b927cd07e978eeac8a4883c9d4f2e01cf99036 Mon Sep 17 00:00:00 2001 From: Marco Salazar Date: Mon, 31 Jan 2022 15:12:38 -0500 Subject: [PATCH 3/5] replace-fork --- packages/react-reconciler/src/ReactFiberCompleteWork.old.js | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/react-reconciler/src/ReactFiberCompleteWork.old.js b/packages/react-reconciler/src/ReactFiberCompleteWork.old.js index 18ce7faa141cd..3568674831092 100644 --- a/packages/react-reconciler/src/ReactFiberCompleteWork.old.js +++ b/packages/react-reconciler/src/ReactFiberCompleteWork.old.js @@ -1032,6 +1032,7 @@ function completeWork( if ( enableClientRenderFallbackOnHydrationMismatch && hasUnhydratedTailNodes() && + (workInProgress.mode & ConcurrentMode) !== NoMode && (workInProgress.flags & DidCapture) === NoFlags ) { warnUnhydratedNextInstance(workInProgress); From 52ac465637982fc527018dc82507065797ea3af5 Mon Sep 17 00:00:00 2001 From: Marco Salazar Date: Mon, 31 Jan 2022 17:14:52 -0500 Subject: [PATCH 4/5] rename --- packages/react-reconciler/src/ReactFiberCompleteWork.new.js | 4 ++-- packages/react-reconciler/src/ReactFiberCompleteWork.old.js | 4 ++-- .../react-reconciler/src/ReactFiberHydrationContext.new.js | 6 +++--- .../react-reconciler/src/ReactFiberHydrationContext.old.js | 6 +++--- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/packages/react-reconciler/src/ReactFiberCompleteWork.new.js b/packages/react-reconciler/src/ReactFiberCompleteWork.new.js index 5e8ee6d6abe13..e8ec41d9d81f5 100644 --- a/packages/react-reconciler/src/ReactFiberCompleteWork.new.js +++ b/packages/react-reconciler/src/ReactFiberCompleteWork.new.js @@ -126,7 +126,7 @@ import { prepareToHydrateHostInstance, prepareToHydrateHostTextInstance, prepareToHydrateHostSuspenseInstance, - warnUnhydratedNextInstance, + warnIfUnhydratedTailNodes, popHydrationState, resetHydrationState, getIsHydrating, @@ -1035,7 +1035,7 @@ function completeWork( (workInProgress.mode & ConcurrentMode) !== NoMode && (workInProgress.flags & DidCapture) === NoFlags ) { - warnUnhydratedNextInstance(workInProgress); + warnIfUnhydratedTailNodes(workInProgress); resetHydrationState(); workInProgress.flags |= ForceClientRender | Incomplete | ShouldCapture; diff --git a/packages/react-reconciler/src/ReactFiberCompleteWork.old.js b/packages/react-reconciler/src/ReactFiberCompleteWork.old.js index 3568674831092..0a0273470a702 100644 --- a/packages/react-reconciler/src/ReactFiberCompleteWork.old.js +++ b/packages/react-reconciler/src/ReactFiberCompleteWork.old.js @@ -126,7 +126,7 @@ import { prepareToHydrateHostInstance, prepareToHydrateHostTextInstance, prepareToHydrateHostSuspenseInstance, - warnUnhydratedNextInstance, + warnIfUnhydratedTailNodes, popHydrationState, resetHydrationState, getIsHydrating, @@ -1035,7 +1035,7 @@ function completeWork( (workInProgress.mode & ConcurrentMode) !== NoMode && (workInProgress.flags & DidCapture) === NoFlags ) { - warnUnhydratedNextInstance(workInProgress); + warnIfUnhydratedTailNodes(workInProgress); resetHydrationState(); workInProgress.flags |= ForceClientRender | Incomplete | ShouldCapture; diff --git a/packages/react-reconciler/src/ReactFiberHydrationContext.new.js b/packages/react-reconciler/src/ReactFiberHydrationContext.new.js index e89d2c5ce0fbf..2a747570d4ffc 100644 --- a/packages/react-reconciler/src/ReactFiberHydrationContext.new.js +++ b/packages/react-reconciler/src/ReactFiberHydrationContext.new.js @@ -553,7 +553,7 @@ function popHydrationState(fiber: Fiber): boolean { ) { let nextInstance = nextHydratableInstance; if (nextInstance) { - warnUnhydratedNextInstance(fiber); + warnIfUnhydratedTailNodes(fiber); throwOnHydrationMismatchIfConcurrentMode(fiber); while (nextInstance) { deleteHydratableInstance(fiber, nextInstance); @@ -576,7 +576,7 @@ function hasUnhydratedTailNodes() { return isHydrating && nextHydratableInstance !== null; } -function warnUnhydratedNextInstance(fiber: Fiber) { +function warnIfUnhydratedTailNodes(fiber: Fiber) { if (nextHydratableInstance) { warnUnhydratedInstance(fiber, nextHydratableInstance); } @@ -608,5 +608,5 @@ export { prepareToHydrateHostSuspenseInstance, popHydrationState, hasUnhydratedTailNodes, - warnUnhydratedNextInstance, + warnIfUnhydratedTailNodes, }; diff --git a/packages/react-reconciler/src/ReactFiberHydrationContext.old.js b/packages/react-reconciler/src/ReactFiberHydrationContext.old.js index df027226e6444..fd538012d57f9 100644 --- a/packages/react-reconciler/src/ReactFiberHydrationContext.old.js +++ b/packages/react-reconciler/src/ReactFiberHydrationContext.old.js @@ -553,7 +553,7 @@ function popHydrationState(fiber: Fiber): boolean { ) { let nextInstance = nextHydratableInstance; if (nextInstance) { - warnUnhydratedNextInstance(fiber); + warnIfUnhydratedTailNodes(fiber); throwOnHydrationMismatchIfConcurrentMode(fiber); while (nextInstance) { deleteHydratableInstance(fiber, nextInstance); @@ -576,7 +576,7 @@ function hasUnhydratedTailNodes() { return isHydrating && nextHydratableInstance !== null; } -function warnUnhydratedNextInstance(fiber: Fiber) { +function warnIfUnhydratedTailNodes(fiber: Fiber) { if (nextHydratableInstance) { warnUnhydratedInstance(fiber, nextHydratableInstance); } @@ -608,5 +608,5 @@ export { prepareToHydrateHostSuspenseInstance, popHydrationState, hasUnhydratedTailNodes, - warnUnhydratedNextInstance, + warnIfUnhydratedTailNodes, }; From 3ae3605b51739833e5a87e6ab9d2d080f38916eb Mon Sep 17 00:00:00 2001 From: Marco Salazar Date: Tue, 1 Feb 2022 10:26:55 -0500 Subject: [PATCH 5/5] warn in a loop --- .../src/ReactFiberHydrationContext.new.js | 29 ++++++++++++------- .../src/ReactFiberHydrationContext.old.js | 29 ++++++++++++------- 2 files changed, 38 insertions(+), 20 deletions(-) diff --git a/packages/react-reconciler/src/ReactFiberHydrationContext.new.js b/packages/react-reconciler/src/ReactFiberHydrationContext.new.js index 2a747570d4ffc..b6153eddccebb 100644 --- a/packages/react-reconciler/src/ReactFiberHydrationContext.new.js +++ b/packages/react-reconciler/src/ReactFiberHydrationContext.new.js @@ -339,12 +339,16 @@ function tryHydrate(fiber, nextInstance) { } } -function throwOnHydrationMismatchIfConcurrentMode(fiber: Fiber) { - if ( +function shouldClientRenderOnMismatch(fiber: Fiber) { + return ( enableClientRenderFallbackOnHydrationMismatch && (fiber.mode & ConcurrentMode) !== NoMode && (fiber.flags & DidCapture) === NoFlags - ) { + ); +} + +function throwOnHydrationMismatchIfConcurrentMode(fiber: Fiber) { + if (shouldClientRenderOnMismatch(fiber)) { throw new Error( 'An error occurred during hydration. The server HTML was replaced with client content', ); @@ -553,11 +557,14 @@ function popHydrationState(fiber: Fiber): boolean { ) { let nextInstance = nextHydratableInstance; if (nextInstance) { - warnIfUnhydratedTailNodes(fiber); - throwOnHydrationMismatchIfConcurrentMode(fiber); - while (nextInstance) { - deleteHydratableInstance(fiber, nextInstance); - nextInstance = getNextHydratableSibling(nextInstance); + if (shouldClientRenderOnMismatch(fiber)) { + warnIfUnhydratedTailNodes(fiber); + throwOnHydrationMismatchIfConcurrentMode(fiber); + } else { + while (nextInstance) { + deleteHydratableInstance(fiber, nextInstance); + nextInstance = getNextHydratableSibling(nextInstance); + } } } } @@ -577,8 +584,10 @@ function hasUnhydratedTailNodes() { } function warnIfUnhydratedTailNodes(fiber: Fiber) { - if (nextHydratableInstance) { - warnUnhydratedInstance(fiber, nextHydratableInstance); + let nextInstance = nextHydratableInstance; + while (nextInstance) { + warnUnhydratedInstance(fiber, nextInstance); + nextInstance = getNextHydratableSibling(nextInstance); } } diff --git a/packages/react-reconciler/src/ReactFiberHydrationContext.old.js b/packages/react-reconciler/src/ReactFiberHydrationContext.old.js index fd538012d57f9..9e2518542454a 100644 --- a/packages/react-reconciler/src/ReactFiberHydrationContext.old.js +++ b/packages/react-reconciler/src/ReactFiberHydrationContext.old.js @@ -339,12 +339,16 @@ function tryHydrate(fiber, nextInstance) { } } -function throwOnHydrationMismatchIfConcurrentMode(fiber: Fiber) { - if ( +function shouldClientRenderOnMismatch(fiber: Fiber) { + return ( enableClientRenderFallbackOnHydrationMismatch && (fiber.mode & ConcurrentMode) !== NoMode && (fiber.flags & DidCapture) === NoFlags - ) { + ); +} + +function throwOnHydrationMismatchIfConcurrentMode(fiber: Fiber) { + if (shouldClientRenderOnMismatch(fiber)) { throw new Error( 'An error occurred during hydration. The server HTML was replaced with client content', ); @@ -553,11 +557,14 @@ function popHydrationState(fiber: Fiber): boolean { ) { let nextInstance = nextHydratableInstance; if (nextInstance) { - warnIfUnhydratedTailNodes(fiber); - throwOnHydrationMismatchIfConcurrentMode(fiber); - while (nextInstance) { - deleteHydratableInstance(fiber, nextInstance); - nextInstance = getNextHydratableSibling(nextInstance); + if (shouldClientRenderOnMismatch(fiber)) { + warnIfUnhydratedTailNodes(fiber); + throwOnHydrationMismatchIfConcurrentMode(fiber); + } else { + while (nextInstance) { + deleteHydratableInstance(fiber, nextInstance); + nextInstance = getNextHydratableSibling(nextInstance); + } } } } @@ -577,8 +584,10 @@ function hasUnhydratedTailNodes() { } function warnIfUnhydratedTailNodes(fiber: Fiber) { - if (nextHydratableInstance) { - warnUnhydratedInstance(fiber, nextHydratableInstance); + let nextInstance = nextHydratableInstance; + while (nextInstance) { + warnUnhydratedInstance(fiber, nextInstance); + nextInstance = getNextHydratableSibling(nextInstance); } }