Skip to content

Commit

Permalink
Remove enableClientRenderFallbackOnTextMismatch flag (facebook#28458)
Browse files Browse the repository at this point in the history
Build on top of facebook#28440.

This lets us remove the path where updates are tracked on differences in
text.
  • Loading branch information
sebmarkbage committed Mar 26, 2024
1 parent f73d11f commit 84c84d7
Show file tree
Hide file tree
Showing 19 changed files with 68 additions and 292 deletions.
19 changes: 3 additions & 16 deletions packages/react-dom-bindings/src/client/ReactDOMComponent.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,6 @@ import sanitizeURL from '../shared/sanitizeURL';
import {
enableBigIntSupport,
enableCustomElementPropertySupport,
enableClientRenderFallbackOnTextMismatch,
disableIEWorkarounds,
enableTrustedTypesIntegration,
enableFilterEmptyStringAttributesDOM,
Expand Down Expand Up @@ -351,11 +350,9 @@ export function checkForUnmatchedText(
}
}

if (enableClientRenderFallbackOnTextMismatch) {
// In concurrent roots, we throw when there's a text mismatch and revert to
// client rendering, up to the nearest Suspense boundary.
throw new Error('Text content does not match server-rendered HTML.');
}
// In concurrent roots, we throw when there's a text mismatch and revert to
// client rendering, up to the nearest Suspense boundary.
throw new Error('Text content does not match server-rendered HTML.');
}

function noop() {}
Expand Down Expand Up @@ -2865,16 +2862,6 @@ export function diffHydratedProperties(
if (props.suppressHydrationWarning !== true) {
checkForUnmatchedText(domElement.textContent, children, shouldWarnDev);
}
if (!enableClientRenderFallbackOnTextMismatch) {
// We really should be patching this in the commit phase but since
// this only affects legacy mode hydration which is deprecated anyway
// we can get away with it.
// Host singletons get their children appended and don't use the text
// content mechanism.
if (tag !== 'body') {
domElement.textContent = (children: any);
}
}
}
}

Expand Down
2 changes: 0 additions & 2 deletions packages/react-dom/src/__tests__/ReactDOMFizzServer-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -4419,7 +4419,6 @@ describe('ReactDOMFizzServer', () => {
);
});

// @gate enableClientRenderFallbackOnTextMismatch
it('#24384: Suspending should halt hydration warnings but still emit hydration warnings after unsuspending if mismatches are genuine', async () => {
const makeApp = () => {
let resolve, resolved;
Expand Down Expand Up @@ -4505,7 +4504,6 @@ describe('ReactDOMFizzServer', () => {
await waitForAll([]);
});

// @gate enableClientRenderFallbackOnTextMismatch
it('only warns once on hydration mismatch while within a suspense boundary', async () => {
const originalConsoleError = console.error;
const mockError = jest.fn();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,6 @@ describe('ReactDOMFizzServerHydrationWarning', () => {
: children;
}

// @gate enableClientRenderFallbackOnTextMismatch
it('suppresses but does not fix text mismatches with suppressHydrationWarning', async () => {
function App({isClient}) {
return (
Expand Down Expand Up @@ -170,47 +169,6 @@ 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 (
Expand Down Expand Up @@ -252,48 +210,6 @@ 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 (
Expand Down Expand Up @@ -345,7 +261,6 @@ describe('ReactDOMFizzServerHydrationWarning', () => {
);
});

// @gate enableClientRenderFallbackOnTextMismatch
it('suppresses but does not fix client-only single text node mismatches with suppressHydrationWarning', async () => {
function App({text}) {
return (
Expand Down Expand Up @@ -386,41 +301,6 @@ 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 () => {
Expand Down
2 changes: 0 additions & 2 deletions packages/react-dom/src/__tests__/ReactDOMFloat-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -6446,7 +6446,6 @@ body {
);
});

// @gate enableClientRenderFallbackOnTextMismatch
it('retains styles even when a new html, head, and/body mount', async () => {
await act(() => {
const {pipe} = renderToPipeableStream(
Expand Down Expand Up @@ -8232,7 +8231,6 @@ background-color: green;
]);
});

// @gate enableClientRenderFallbackOnTextMismatch || !__DEV__
it('can render a title before a singleton even if that singleton clears its contents', async () => {
await act(() => {
const {pipe} = renderToPipeableStream(
Expand Down
93 changes: 31 additions & 62 deletions packages/react-dom/src/__tests__/ReactDOMHydrationDiff-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -80,28 +80,17 @@ describe('ReactDOMServerHydration', () => {
</div>
);
}
if (gate(flags => flags.enableClientRenderFallbackOnTextMismatch)) {
expect(testMismatch(Mismatch)).toMatchInlineSnapshot(`
[
"Warning: Text content did not match. Server: "server" Client: "client"
in main (at **)
in div (at **)
in Mismatch (at **)",
"Warning: An error occurred during hydration. The server HTML was replaced with client content in <div>.",
"Caught [Text content does not match server-rendered HTML.]",
"Caught [There was an error while hydrating. Because the error happened outside of a Suspense boundary, the entire root will switch to client rendering.]",
]
`);
} else {
expect(testMismatch(Mismatch)).toMatchInlineSnapshot(`
[
"Warning: Text content did not match. Server: "server" Client: "client"
in main (at **)
in div (at **)
in Mismatch (at **)",
]
`);
}
expect(testMismatch(Mismatch)).toMatchInlineSnapshot(`
[
"Warning: Text content did not match. Server: "server" Client: "client"
in main (at **)
in div (at **)
in Mismatch (at **)",
"Warning: An error occurred during hydration. The server HTML was replaced with client content in <div>.",
"Caught [Text content does not match server-rendered HTML.]",
"Caught [There was an error while hydrating. Because the error happened outside of a Suspense boundary, the entire root will switch to client rendering.]",
]
`);
});

// @gate __DEV__
Expand All @@ -118,26 +107,16 @@ describe('ReactDOMServerHydration', () => {
}

/* eslint-disable no-irregular-whitespace */
if (gate(flags => flags.enableClientRenderFallbackOnTextMismatch)) {
expect(testMismatch(Mismatch)).toMatchInlineSnapshot(`
[
"Warning: Text content did not match. Server: "This markup contains an nbsp entity:   server text" Client: "This markup contains an nbsp entity:   client text"
in div (at **)
in Mismatch (at **)",
"Warning: An error occurred during hydration. The server HTML was replaced with client content in <div>.",
"Caught [Text content does not match server-rendered HTML.]",
"Caught [There was an error while hydrating. Because the error happened outside of a Suspense boundary, the entire root will switch to client rendering.]",
]
`);
} else {
expect(testMismatch(Mismatch)).toMatchInlineSnapshot(`
[
"Warning: Text content did not match. Server: "This markup contains an nbsp entity:   server text" Client: "This markup contains an nbsp entity:   client text"
in div (at **)
in Mismatch (at **)",
]
`);
}
expect(testMismatch(Mismatch)).toMatchInlineSnapshot(`
[
"Warning: Text content did not match. Server: "This markup contains an nbsp entity:   server text" Client: "This markup contains an nbsp entity:   client text"
in div (at **)
in Mismatch (at **)",
"Warning: An error occurred during hydration. The server HTML was replaced with client content in <div>.",
"Caught [Text content does not match server-rendered HTML.]",
"Caught [There was an error while hydrating. Because the error happened outside of a Suspense boundary, the entire root will switch to client rendering.]",
]
`);
/* eslint-enable no-irregular-whitespace */
});

Expand Down Expand Up @@ -388,26 +367,16 @@ describe('ReactDOMServerHydration', () => {
function Mismatch({isClient}) {
return <div className="parent">{isClient && 'only'}</div>;
}
if (gate(flags => flags.enableClientRenderFallbackOnTextMismatch)) {
expect(testMismatch(Mismatch)).toMatchInlineSnapshot(`
[
"Warning: Text content did not match. Server: "" Client: "only"
in div (at **)
in Mismatch (at **)",
"Warning: An error occurred during hydration. The server HTML was replaced with client content in <div>.",
"Caught [Text content does not match server-rendered HTML.]",
"Caught [There was an error while hydrating. Because the error happened outside of a Suspense boundary, the entire root will switch to client rendering.]",
]
`);
} else {
expect(testMismatch(Mismatch)).toMatchInlineSnapshot(`
[
"Warning: Text content did not match. Server: "" Client: "only"
in div (at **)
in Mismatch (at **)",
]
`);
}
expect(testMismatch(Mismatch)).toMatchInlineSnapshot(`
[
"Warning: Text content did not match. Server: "" Client: "only"
in div (at **)
in Mismatch (at **)",
"Warning: An error occurred during hydration. The server HTML was replaced with client content in <div>.",
"Caught [Text content does not match server-rendered HTML.]",
"Caught [There was an error while hydrating. Because the error happened outside of a Suspense boundary, the entire root will switch to client rendering.]",
]
`);
});

// @gate __DEV__
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,13 @@ let React;
let ReactDOM;
let ReactDOMClient;
let ReactDOMServer;
let ReactFeatureFlags;

function initModules() {
jest.resetModules();
React = require('react');
ReactDOM = require('react-dom');
ReactDOMClient = require('react-dom/client');
ReactDOMServer = require('react-dom/server');
ReactFeatureFlags = require('shared/ReactFeatureFlags');

// Make them available to the helpers.
return {
Expand Down Expand Up @@ -843,16 +841,15 @@ describe('ReactDOMServerIntegration', () => {
if (
render === serverRender ||
render === streamRender ||
(render === clientRenderOnServerString &&
ReactFeatureFlags.enableClientRenderFallbackOnTextMismatch)
render === clientRenderOnServerString
) {
expect(e.childNodes.length).toBe(1);
// Everything becomes LF when parsed from server HTML or hydrated if enableClientRenderFallbackOnTextMismatch is on.
// Everything becomes LF when parsed from server HTML or hydrated.
// Null character is ignored.
expectNode(e.childNodes[0], TEXT_NODE_TYPE, 'foo\nbar\nbaz\nqux');
} else {
expect(e.childNodes.length).toBe(1);
// Client rendering (or hydration without enableClientRenderFallbackOnTextMismatch) uses JS value with CR.
// Client rendering uses JS value with CR.
// Null character stays.

expectNode(
Expand All @@ -876,19 +873,18 @@ describe('ReactDOMServerIntegration', () => {
if (
render === serverRender ||
render === streamRender ||
(render === clientRenderOnServerString &&
ReactFeatureFlags.enableClientRenderFallbackOnTextMismatch)
render === clientRenderOnServerString
) {
// We have three nodes because there is a comment between them.
expect(e.childNodes.length).toBe(3);
// Everything becomes LF when parsed from server HTML or hydrated if enableClientRenderFallbackOnTextMismatch is on.
// Everything becomes LF when parsed from server HTML or hydrated.
// Null character is ignored.
expectNode(e.childNodes[0], TEXT_NODE_TYPE, 'foo\nbar');
expectNode(e.childNodes[2], TEXT_NODE_TYPE, '\nbaz\nqux');
} else if (render === clientRenderOnServerString) {
// We have three nodes because there is a comment between them.
expect(e.childNodes.length).toBe(3);
// Hydration without enableClientRenderFallbackOnTextMismatch uses JS value with CR and null character.
// Hydration uses JS value with CR and null character.

expectNode(e.childNodes[0], TEXT_NODE_TYPE, 'foo\rbar');
expectNode(e.childNodes[2], TEXT_NODE_TYPE, '\r\nbaz\nqux\u0000');
Expand Down

0 comments on commit 84c84d7

Please sign in to comment.