Repository: facebook/react
Pull request: Fix enableClientRenderFallbackOnTextMismatch flag

With this flag off, we don't throw and therefore don't patch up the tree when suppression is off.

Haven't tested.

Changed files:
filename: packages/react-dom-bindings/src/client/ReactDOMComponent.js
patch: @@ -1440,7 +1440,7 @@ export function diffHydratedProperties(
           shouldWarnDev,
         );
       }
-      if (!isConcurrentMode) {
+      if (!isConcurrentMode || !enableClientRenderFallbackOnTextMismatch) {
         updatePayload = ['children', children];
       }
     }

filename: packages/react-dom/src/__tests__/ReactDOMFizzSuppressHydrationWarning-test.js
patch: @@ -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 (
+        <div>
+          <span suppressHydrationWarning={true}>
+            {isClient ? 'Client Text' : 'Server Text'}
+          </span>
+          <span suppressHydrationWarning={true}>{isClient ? 2 : 1}</span>
+        </div>
+      );
+    }
+    await act(() => {
+      const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
+        <App isClient={false} />,
+      );
+      pipe(writable);
+    });
+    expect(getVisibleChildren(container)).toEqual(
+      <div>
+        <span>Server Text</span>
+        <span>1</span>
+      </div>,
+    );
+    ReactDOMClient.hydrateRoot(container, <App isClient={true} />, {
+      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(
+      <div>
+        <span>Client Text</span>
+        <span>2</span>
+      </div>,
+    );
+  });
+
+  // @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 (
+        <div>
+          <span suppressHydrationWarning={true}>
+            {isClient ? 'Client1' : 'Server1'}
+            {isClient ? 'Client2' : 'Server2'}
+          </span>
+        </div>
+      );
+    }
+    await act(() => {
+      const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
+        <App isClient={false} />,
+      );
+      pipe(writable);
+    });
+    expect(getVisibleChildren(container)).toEqual(
+      <div>
+        <span>
+          {'Server1'}
+          {'Server2'}
+        </span>
+      </div>,
+    );
+    ReactDOMClient.hydrateRoot(container, <App isClient={true} />, {
+      onRecoverableError(error) {
+        Scheduler.log(error.message);
+      },
+    });
+    await waitForAll([]);
+    expect(getVisibleChildren(container)).toEqual(
+      <div>
+        <span>
+          {'Client1'}
+          {'Client2'}
+        </span>
+      </div>,
+    );
+  });
+
   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 (
+        <div>
+          <span suppressHydrationWarning={true}>
+            {isClient ? 'Client' : null}
+          </span>
+        </div>
+      );
+    }
+    await act(() => {
+      const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
+        <App isClient={false} />,
+      );
+      pipe(writable);
+    });
+    expect(getVisibleChildren(container)).toEqual(
+      <div>
+        <span />
+      </div>,
+    );
+    ReactDOMClient.hydrateRoot(container, <App isClient={true} />, {
+      onRecoverableError(error) {
+        Scheduler.log(error.message);
+      },
+    });
+    await waitForAll([]);
+    expect(getVisibleChildren(container)).toEqual(
+      <div>
+        <span>{'Client'}</span>
+      </div>,
+    );
+  });
+
   // TODO: This behavior is not consistent with client-only single text node.
 
   it('errors on server-only single text node mismatches with suppressHydrationWarning', async () => {

filename: packages/react-dom/src/__tests__/ReactDOMFloat-test.js
patch: @@ -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(

filename: packages/react-reconciler/src/ReactFiberHydrationContext.js
patch: @@ -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;