From 2926d4f96a28c72969c4257e3fc4bac582f6244d Mon Sep 17 00:00:00 2001 From: Sebastian Markbage Date: Wed, 22 Mar 2023 08:31:06 -0400 Subject: [PATCH 1/6] Fix enableClientRenderFallbackOnTextMismatch flag --- .../react-dom-bindings/src/client/ReactDOMComponent.js | 2 +- .../react-reconciler/src/ReactFiberHydrationContext.js | 8 ++++++-- 2 files changed, 7 insertions(+), 3 deletions(-) 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-reconciler/src/ReactFiberHydrationContext.js b/packages/react-reconciler/src/ReactFiberHydrationContext.js index e8ce874e34edc..976b45abb104b 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; From 327107602a914d84d59f4e54b613ec4761bce00e Mon Sep 17 00:00:00 2001 From: Rick Hanlon Date: Wed, 22 Mar 2023 12:54:21 +0000 Subject: [PATCH 2/6] Fix ReactDOMFizzSuppressHydrationWarning-test.js --- ...actDOMFizzSuppressHydrationWarning-test.js | 120 ++++++++++++++++++ 1 file changed, 120 insertions(+) diff --git a/packages/react-dom/src/__tests__/ReactDOMFizzSuppressHydrationWarning-test.js b/packages/react-dom/src/__tests__/ReactDOMFizzSuppressHydrationWarning-test.js index c53ddee3fb13b..8e5f9a7749e50 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 () => { From e7085885f2122e18680cb82e39d2b76e73047920 Mon Sep 17 00:00:00 2001 From: Rick Hanlon Date: Wed, 22 Mar 2023 13:05:13 +0000 Subject: [PATCH 3/6] Actually tho --- ...actDOMFizzSuppressHydrationWarning-test.js | 43 ------------------- 1 file changed, 43 deletions(-) diff --git a/packages/react-dom/src/__tests__/ReactDOMFizzSuppressHydrationWarning-test.js b/packages/react-dom/src/__tests__/ReactDOMFizzSuppressHydrationWarning-test.js index 8e5f9a7749e50..9c7beb32a45eb 100644 --- a/packages/react-dom/src/__tests__/ReactDOMFizzSuppressHydrationWarning-test.js +++ b/packages/react-dom/src/__tests__/ReactDOMFizzSuppressHydrationWarning-test.js @@ -210,7 +210,6 @@ describe('ReactDOMFizzServerHydrationWarning', () => { ); }); - // @gate !enableClientRenderFallbackOnTextMismatch it('suppresses but does not fix multiple text node mismatches with suppressHydrationWarning', async () => { function App({isClient}) { return ( @@ -252,48 +251,6 @@ 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 ( From 9dde0e70646a4a66e7eada7949ca799f5b89e81c Mon Sep 17 00:00:00 2001 From: Rick Hanlon Date: Wed, 22 Mar 2023 13:16:06 +0000 Subject: [PATCH 4/6] ReactDOMFloat-test.js --- packages/react-dom/src/__tests__/ReactDOMFloat-test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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( From 56acc519f874720a7687f9195c1d0729c4e2a4cb Mon Sep 17 00:00:00 2001 From: Rick Hanlon Date: Wed, 22 Mar 2023 14:18:48 +0000 Subject: [PATCH 5/6] Also add check to HostComponent --- packages/react-reconciler/src/ReactFiberHydrationContext.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react-reconciler/src/ReactFiberHydrationContext.js b/packages/react-reconciler/src/ReactFiberHydrationContext.js index 976b45abb104b..351fa122929c7 100644 --- a/packages/react-reconciler/src/ReactFiberHydrationContext.js +++ b/packages/react-reconciler/src/ReactFiberHydrationContext.js @@ -756,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; From bfecd0897bcfaac8eadfbb64362f069d84c05aff Mon Sep 17 00:00:00 2001 From: Rick Hanlon Date: Wed, 22 Mar 2023 14:28:35 +0000 Subject: [PATCH 6/6] fix more tests --- ...actDOMFizzSuppressHydrationWarning-test.js | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/packages/react-dom/src/__tests__/ReactDOMFizzSuppressHydrationWarning-test.js b/packages/react-dom/src/__tests__/ReactDOMFizzSuppressHydrationWarning-test.js index 9c7beb32a45eb..148287662b414 100644 --- a/packages/react-dom/src/__tests__/ReactDOMFizzSuppressHydrationWarning-test.js +++ b/packages/react-dom/src/__tests__/ReactDOMFizzSuppressHydrationWarning-test.js @@ -210,6 +210,7 @@ describe('ReactDOMFizzServerHydrationWarning', () => { ); }); + // @gate enableClientRenderFallbackOnTextMismatch it('suppresses but does not fix multiple text node mismatches with suppressHydrationWarning', async () => { function App({isClient}) { return ( @@ -251,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 (