diff --git a/packages/react-dom/src/__tests__/ReactDOMFizzSuppressHydrationWarning-test.js b/packages/react-dom/src/__tests__/ReactDOMFizzSuppressHydrationWarning-test.js index f6e6d3222b90a..4f65ecf241c80 100644 --- a/packages/react-dom/src/__tests__/ReactDOMFizzSuppressHydrationWarning-test.js +++ b/packages/react-dom/src/__tests__/ReactDOMFizzSuppressHydrationWarning-test.js @@ -248,9 +248,10 @@ describe('ReactDOMFizzServerHydrationWarning', () => { ]); }).toErrorDev( [ + 'Expected server HTML to contain a matching in ', 'An error occurred during hydration. The server HTML was replaced with client content in
.', ], - {withoutStack: true}, + {withoutStack: 1}, ); } else { // This used to not warn. @@ -336,9 +337,10 @@ describe('ReactDOMFizzServerHydrationWarning', () => { ]); }).toErrorDev( [ + 'Did not expect server HTML to contain the text node "Server" in ', 'An error occurred during hydration. The server HTML was replaced with client content in
.', ], - {withoutStack: true}, + {withoutStack: 1}, ); } else { // This used to not warn. @@ -389,9 +391,10 @@ describe('ReactDOMFizzServerHydrationWarning', () => { ]); }).toErrorDev( [ + 'Expected server HTML to contain a matching text node for "Client" in .', 'An error occurred during hydration. The server HTML was replaced with client content in
.', ], - {withoutStack: true}, + {withoutStack: 1}, ); } else { // This used to not warn. @@ -445,9 +448,10 @@ describe('ReactDOMFizzServerHydrationWarning', () => { ]); }).toErrorDev( [ + 'Did not expect server HTML to contain the text node "Server" in .', 'An error occurred during hydration. The server HTML was replaced with client content in
.', ], - {withoutStack: true}, + {withoutStack: 1}, ); } else { // This used to not warn. @@ -500,9 +504,10 @@ describe('ReactDOMFizzServerHydrationWarning', () => { ]); }).toErrorDev( [ + 'Expected server HTML to contain a matching text node for "Client" in .', 'An error occurred during hydration. The server HTML was replaced with client content in
.', ], - {withoutStack: true}, + {withoutStack: 1}, ); } else { // This used to not warn. @@ -630,9 +635,10 @@ describe('ReactDOMFizzServerHydrationWarning', () => { ]); }).toErrorDev( [ + 'Expected server HTML to contain a matching

in

.', 'An error occurred during hydration. The server HTML was replaced with client content in
.', ], - {withoutStack: true}, + {withoutStack: 1}, ); } else { // This used to not warn. @@ -681,9 +687,10 @@ describe('ReactDOMFizzServerHydrationWarning', () => { ]); }).toErrorDev( [ + 'Did not expect server HTML to contain a

in

.', 'An error occurred during hydration. The server HTML was replaced with client content in
.', ], - {withoutStack: true}, + {withoutStack: 1}, ); } else { // This used to not warn. diff --git a/packages/react-dom/src/client/ReactDOMHostConfig.js b/packages/react-dom/src/client/ReactDOMHostConfig.js index 925f7088e4b04..d27bdff4305ab 100644 --- a/packages/react-dom/src/client/ReactDOMHostConfig.js +++ b/packages/react-dom/src/client/ReactDOMHostConfig.js @@ -62,6 +62,7 @@ import dangerousStyleValue from '../shared/dangerousStyleValue'; import {retryIfBlockedOn} from '../events/ReactDOMEventReplaying'; import { + enableClientRenderFallbackOnHydrationMismatch, enableSuspenseServerRenderer, enableCreateEventHandleAPI, enableScopeAPI, @@ -1004,14 +1005,20 @@ export function didNotHydrateInstance( parentProps: Props, parentInstance: Instance, instance: HydratableInstance, + isConcurrentMode: boolean, ) { - if (__DEV__ && parentProps[SUPPRESS_HYDRATION_WARNING] !== true) { - if (instance.nodeType === ELEMENT_NODE) { - warnForDeletedHydratableElement(parentInstance, (instance: any)); - } else if (instance.nodeType === COMMENT_NODE) { - // TODO: warnForDeletedHydratableSuspenseBoundary - } else { - warnForDeletedHydratableText(parentInstance, (instance: any)); + if (__DEV__) { + if ( + (enableClientRenderFallbackOnHydrationMismatch && isConcurrentMode) || + parentProps[SUPPRESS_HYDRATION_WARNING] !== true + ) { + if (instance.nodeType === ELEMENT_NODE) { + warnForDeletedHydratableElement(parentInstance, (instance: any)); + } else if (instance.nodeType === COMMENT_NODE) { + // TODO: warnForDeletedHydratableSuspenseBoundary + } else { + warnForDeletedHydratableText(parentInstance, (instance: any)); + } } } } @@ -1082,9 +1089,15 @@ export function didNotFindHydratableInstance( parentInstance: Instance, type: string, props: Props, + isConcurrentMode: boolean, ) { - if (__DEV__ && parentProps[SUPPRESS_HYDRATION_WARNING] !== true) { - warnForInsertedHydratedElement(parentInstance, type, props); + if (__DEV__) { + if ( + (enableClientRenderFallbackOnHydrationMismatch && isConcurrentMode) || + parentProps[SUPPRESS_HYDRATION_WARNING] !== true + ) { + warnForInsertedHydratedElement(parentInstance, type, props); + } } } @@ -1093,9 +1106,15 @@ export function didNotFindHydratableTextInstance( parentProps: Props, parentInstance: Instance, text: string, + isConcurrentMode: boolean, ) { - if (__DEV__ && parentProps[SUPPRESS_HYDRATION_WARNING] !== true) { - warnForInsertedHydratedText(parentInstance, text); + if (__DEV__) { + if ( + (enableClientRenderFallbackOnHydrationMismatch && isConcurrentMode) || + parentProps[SUPPRESS_HYDRATION_WARNING] !== true + ) { + warnForInsertedHydratedText(parentInstance, text); + } } } @@ -1104,7 +1123,7 @@ export function didNotFindHydratableSuspenseInstance( parentProps: Props, parentInstance: Instance, ) { - if (__DEV__ && parentProps[SUPPRESS_HYDRATION_WARNING] !== true) { + if (__DEV__) { // TODO: warnForInsertedHydratedSuspense(parentInstance); } } diff --git a/packages/react-reconciler/src/ReactFiberHydrationContext.new.js b/packages/react-reconciler/src/ReactFiberHydrationContext.new.js index 6a7a175d6cff3..94ca9e61eec15 100644 --- a/packages/react-reconciler/src/ReactFiberHydrationContext.new.js +++ b/packages/react-reconciler/src/ReactFiberHydrationContext.new.js @@ -148,21 +148,26 @@ function warnUnhydratedInstance( ) { if (__DEV__) { switch (returnFiber.tag) { - case HostRoot: + case HostRoot: { didNotHydrateInstanceWithinContainer( returnFiber.stateNode.containerInfo, instance, ); break; - case HostComponent: + } + case HostComponent: { + const isConcurrentMode = (returnFiber.mode & ConcurrentMode) !== NoMode; didNotHydrateInstance( returnFiber.type, returnFiber.memoizedProps, returnFiber.stateNode, instance, + // TODO: Delete this argument when we remove the legacy root API. + isConcurrentMode, ); break; - case SuspenseComponent: + } + case SuspenseComponent: { const suspenseState: SuspenseState = returnFiber.memoizedState; if (suspenseState.dehydrated !== null) didNotHydrateInstanceWithinSuspenseInstance( @@ -170,6 +175,7 @@ function warnUnhydratedInstance( instance, ); break; + } } } } @@ -234,33 +240,44 @@ function warnNonhydratedInstance(returnFiber: Fiber, fiber: Fiber) { const parentProps = returnFiber.memoizedProps; const parentInstance = returnFiber.stateNode; switch (fiber.tag) { - case HostComponent: + case HostComponent: { const type = fiber.type; const props = fiber.pendingProps; + const isConcurrentMode = + (returnFiber.mode & ConcurrentMode) !== NoMode; didNotFindHydratableInstance( parentType, parentProps, parentInstance, type, props, + // TODO: Delete this argument when we remove the legacy root API. + isConcurrentMode, ); break; - case HostText: + } + case HostText: { const text = fiber.pendingProps; + const isConcurrentMode = + (returnFiber.mode & ConcurrentMode) !== NoMode; didNotFindHydratableTextInstance( parentType, parentProps, parentInstance, text, + // TODO: Delete this argument when we remove the legacy root API. + isConcurrentMode, ); break; - case SuspenseComponent: + } + case SuspenseComponent: { didNotFindHydratableSuspenseInstance( parentType, parentProps, parentInstance, ); break; + } } break; } @@ -476,10 +493,11 @@ function prepareToHydrateHostTextInstance(fiber: Fiber): boolean { // hydration parent is the parent host component of this host text. const returnFiber = hydrationParentFiber; if (returnFiber !== null) { - const isConcurrentMode = (returnFiber.mode & ConcurrentMode) !== NoMode; switch (returnFiber.tag) { case HostRoot: { const parentContainer = returnFiber.stateNode.containerInfo; + const isConcurrentMode = + (returnFiber.mode & ConcurrentMode) !== NoMode; didNotMatchHydratedContainerTextInstance( parentContainer, textInstance, @@ -493,6 +511,8 @@ function prepareToHydrateHostTextInstance(fiber: Fiber): boolean { const parentType = returnFiber.type; const parentProps = returnFiber.memoizedProps; const parentInstance = returnFiber.stateNode; + const isConcurrentMode = + (returnFiber.mode & ConcurrentMode) !== NoMode; didNotMatchHydratedTextInstance( parentType, parentProps, diff --git a/packages/react-reconciler/src/ReactFiberHydrationContext.old.js b/packages/react-reconciler/src/ReactFiberHydrationContext.old.js index 8c334924943ab..6af68fe66a79d 100644 --- a/packages/react-reconciler/src/ReactFiberHydrationContext.old.js +++ b/packages/react-reconciler/src/ReactFiberHydrationContext.old.js @@ -148,21 +148,26 @@ function warnUnhydratedInstance( ) { if (__DEV__) { switch (returnFiber.tag) { - case HostRoot: + case HostRoot: { didNotHydrateInstanceWithinContainer( returnFiber.stateNode.containerInfo, instance, ); break; - case HostComponent: + } + case HostComponent: { + const isConcurrentMode = (returnFiber.mode & ConcurrentMode) !== NoMode; didNotHydrateInstance( returnFiber.type, returnFiber.memoizedProps, returnFiber.stateNode, instance, + // TODO: Delete this argument when we remove the legacy root API. + isConcurrentMode, ); break; - case SuspenseComponent: + } + case SuspenseComponent: { const suspenseState: SuspenseState = returnFiber.memoizedState; if (suspenseState.dehydrated !== null) didNotHydrateInstanceWithinSuspenseInstance( @@ -170,6 +175,7 @@ function warnUnhydratedInstance( instance, ); break; + } } } } @@ -234,33 +240,44 @@ function warnNonhydratedInstance(returnFiber: Fiber, fiber: Fiber) { const parentProps = returnFiber.memoizedProps; const parentInstance = returnFiber.stateNode; switch (fiber.tag) { - case HostComponent: + case HostComponent: { const type = fiber.type; const props = fiber.pendingProps; + const isConcurrentMode = + (returnFiber.mode & ConcurrentMode) !== NoMode; didNotFindHydratableInstance( parentType, parentProps, parentInstance, type, props, + // TODO: Delete this argument when we remove the legacy root API. + isConcurrentMode, ); break; - case HostText: + } + case HostText: { const text = fiber.pendingProps; + const isConcurrentMode = + (returnFiber.mode & ConcurrentMode) !== NoMode; didNotFindHydratableTextInstance( parentType, parentProps, parentInstance, text, + // TODO: Delete this argument when we remove the legacy root API. + isConcurrentMode, ); break; - case SuspenseComponent: + } + case SuspenseComponent: { didNotFindHydratableSuspenseInstance( parentType, parentProps, parentInstance, ); break; + } } break; } @@ -476,10 +493,11 @@ function prepareToHydrateHostTextInstance(fiber: Fiber): boolean { // hydration parent is the parent host component of this host text. const returnFiber = hydrationParentFiber; if (returnFiber !== null) { - const isConcurrentMode = (returnFiber.mode & ConcurrentMode) !== NoMode; switch (returnFiber.tag) { case HostRoot: { const parentContainer = returnFiber.stateNode.containerInfo; + const isConcurrentMode = + (returnFiber.mode & ConcurrentMode) !== NoMode; didNotMatchHydratedContainerTextInstance( parentContainer, textInstance, @@ -493,6 +511,8 @@ function prepareToHydrateHostTextInstance(fiber: Fiber): boolean { const parentType = returnFiber.type; const parentProps = returnFiber.memoizedProps; const parentInstance = returnFiber.stateNode; + const isConcurrentMode = + (returnFiber.mode & ConcurrentMode) !== NoMode; didNotMatchHydratedTextInstance( parentType, parentProps,