diff --git a/packages/react-dom-bindings/src/client/ReactDOMComponent.js b/packages/react-dom-bindings/src/client/ReactDOMComponent.js index 144830c7651c5..4127da0831347 100644 --- a/packages/react-dom-bindings/src/client/ReactDOMComponent.js +++ b/packages/react-dom-bindings/src/client/ReactDOMComponent.js @@ -1439,7 +1439,7 @@ export function diffHydratedProperties( shouldWarnDev, ); } - if (!isConcurrentMode) { + if (!isConcurrentMode || !enableClientRenderFallbackOnTextMismatch) { updatePayload = ['children', children]; } } diff --git a/packages/react-dom/src/__tests__/ReactDOMFizzSuppressHydrationWarning-test.js b/packages/react-dom/src/__tests__/ReactDOMFizzSuppressHydrationWarning-test.js index c53ddee3fb13b..148287662b414 100644 --- a/packages/react-dom/src/__tests__/ReactDOMFizzSuppressHydrationWarning-test.js +++ b/packages/react-dom/src/__tests__/ReactDOMFizzSuppressHydrationWarning-test.js @@ -130,6 +130,7 @@ describe('ReactDOMFizzServerHydrationWarning', () => { : children; } + // @gate enableClientRenderFallbackOnTextMismatch it('suppresses but does not fix text mismatches with suppressHydrationWarning', async () => { function App({isClient}) { return ( @@ -169,6 +170,47 @@ describe('ReactDOMFizzServerHydrationWarning', () => { ); }); + // @gate !enableClientRenderFallbackOnTextMismatch + it('suppresses and fixes text mismatches with suppressHydrationWarning', async () => { + function App({isClient}) { + return ( +
+ + {isClient ? 'Client Text' : 'Server Text'} + + {isClient ? 2 : 1} +
+ ); + } + await act(() => { + const {pipe} = ReactDOMFizzServer.renderToPipeableStream( + , + ); + pipe(writable); + }); + expect(getVisibleChildren(container)).toEqual( +
+ Server Text + 1 +
, + ); + ReactDOMClient.hydrateRoot(container, , { + onRecoverableError(error) { + // Don't miss a hydration error. There should be none. + Scheduler.log(error.message); + }, + }); + await waitForAll([]); + // The text mismatch should be *silently* fixed. Even in production. + expect(getVisibleChildren(container)).toEqual( +
+ Client Text + 2 +
, + ); + }); + + // @gate enableClientRenderFallbackOnTextMismatch it('suppresses but does not fix multiple text node mismatches with suppressHydrationWarning', async () => { function App({isClient}) { return ( @@ -210,6 +252,48 @@ describe('ReactDOMFizzServerHydrationWarning', () => { ); }); + // @gate !enableClientRenderFallbackOnTextMismatch + it('suppresses and fixes multiple text node mismatches with suppressHydrationWarning', async () => { + function App({isClient}) { + return ( +
+ + {isClient ? 'Client1' : 'Server1'} + {isClient ? 'Client2' : 'Server2'} + +
+ ); + } + await act(() => { + const {pipe} = ReactDOMFizzServer.renderToPipeableStream( + , + ); + pipe(writable); + }); + expect(getVisibleChildren(container)).toEqual( +
+ + {'Server1'} + {'Server2'} + +
, + ); + ReactDOMClient.hydrateRoot(container, , { + onRecoverableError(error) { + Scheduler.log(error.message); + }, + }); + await waitForAll([]); + expect(getVisibleChildren(container)).toEqual( +
+ + {'Client1'} + {'Client2'} + +
, + ); + }); + it('errors on text-to-element mismatches with suppressHydrationWarning', async () => { function App({isClient}) { return ( @@ -261,6 +345,7 @@ describe('ReactDOMFizzServerHydrationWarning', () => { ); }); + // @gate enableClientRenderFallbackOnTextMismatch it('suppresses but does not fix client-only single text node mismatches with suppressHydrationWarning', async () => { function App({text}) { return ( @@ -301,6 +386,41 @@ describe('ReactDOMFizzServerHydrationWarning', () => { ); }); + // @gate !enableClientRenderFallbackOnTextMismatch + it('suppresses and fixes client-only single text node mismatches with suppressHydrationWarning', async () => { + function App({isClient}) { + return ( +
+ + {isClient ? 'Client' : null} + +
+ ); + } + await act(() => { + const {pipe} = ReactDOMFizzServer.renderToPipeableStream( + , + ); + pipe(writable); + }); + expect(getVisibleChildren(container)).toEqual( +
+ +
, + ); + ReactDOMClient.hydrateRoot(container, , { + onRecoverableError(error) { + Scheduler.log(error.message); + }, + }); + await waitForAll([]); + expect(getVisibleChildren(container)).toEqual( +
+ {'Client'} +
, + ); + }); + // TODO: This behavior is not consistent with client-only single text node. it('errors on server-only single text node mismatches with suppressHydrationWarning', async () => { diff --git a/packages/react-dom/src/__tests__/ReactDOMFloat-test.js b/packages/react-dom/src/__tests__/ReactDOMFloat-test.js index c3a6da112cb4c..2df936507724b 100644 --- a/packages/react-dom/src/__tests__/ReactDOMFloat-test.js +++ b/packages/react-dom/src/__tests__/ReactDOMFloat-test.js @@ -5617,7 +5617,7 @@ background-color: green; ]); }); - // @gate enableFloat && enableHostSingletons && enableClientRenderFallbackOnTextMismatch + // @gate enableFloat && enableHostSingletons && (enableClientRenderFallbackOnTextMismatch || !__DEV__) it('can render a title before a singleton even if that singleton clears its contents', async () => { await actIntoEmptyDocument(() => { const {pipe} = renderToPipeableStream( diff --git a/packages/react-reconciler/src/ReactFiberHydrationContext.js b/packages/react-reconciler/src/ReactFiberHydrationContext.js index e8ce874e34edc..351fa122929c7 100644 --- a/packages/react-reconciler/src/ReactFiberHydrationContext.js +++ b/packages/react-reconciler/src/ReactFiberHydrationContext.js @@ -35,7 +35,11 @@ import { NoFlags, DidCapture, } from './ReactFiberFlags'; -import {enableHostSingletons, enableFloat} from 'shared/ReactFeatureFlags'; +import { + enableHostSingletons, + enableFloat, + enableClientRenderFallbackOnTextMismatch, +} from 'shared/ReactFeatureFlags'; import { createFiberFromHostInstanceForDeletion, @@ -728,7 +732,7 @@ function prepareToHydrateHostTextInstance(fiber: Fiber): boolean { isConcurrentMode, shouldWarnIfMismatchDev, ); - if (isConcurrentMode) { + if (isConcurrentMode && enableClientRenderFallbackOnTextMismatch) { // In concurrent mode we never update the mismatched text, // even if the error was ignored. return false; @@ -752,7 +756,7 @@ function prepareToHydrateHostTextInstance(fiber: Fiber): boolean { isConcurrentMode, shouldWarnIfMismatchDev, ); - if (isConcurrentMode) { + if (isConcurrentMode && enableClientRenderFallbackOnTextMismatch) { // In concurrent mode we never update the mismatched text, // even if the error was ignored. return false;