Fix enableClientRenderFallbackOnTextMismatch flag (#26457)

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

Haven't tested.

---------

Co-authored-by: Rick Hanlon <rickhanlonii@fb.com>
This commit is contained in:
Sebastian Markbåge 2023-03-22 13:12:41 -04:00 committed by GitHub
parent 8e17bfd144
commit afb3d51dc6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 129 additions and 5 deletions

View File

@ -1440,7 +1440,7 @@ export function diffHydratedProperties(
shouldWarnDev,
);
}
if (!isConcurrentMode) {
if (!isConcurrentMode || !enableClientRenderFallbackOnTextMismatch) {
updatePayload = ['children', children];
}
}

View File

@ -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 () => {

View File

@ -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(

View File

@ -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;