Skip to content

Commit 84c84d7

Browse files
authored
Remove enableClientRenderFallbackOnTextMismatch flag (#28458)
Build on top of #28440. This lets us remove the path where updates are tracked on differences in text.
1 parent f73d11f commit 84c84d7

19 files changed

+68
-292
lines changed

packages/react-dom-bindings/src/client/ReactDOMComponent.js

+3-16
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,6 @@ import sanitizeURL from '../shared/sanitizeURL';
6868
import {
6969
enableBigIntSupport,
7070
enableCustomElementPropertySupport,
71-
enableClientRenderFallbackOnTextMismatch,
7271
disableIEWorkarounds,
7372
enableTrustedTypesIntegration,
7473
enableFilterEmptyStringAttributesDOM,
@@ -351,11 +350,9 @@ export function checkForUnmatchedText(
351350
}
352351
}
353352

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

361358
function noop() {}
@@ -2865,16 +2862,6 @@ export function diffHydratedProperties(
28652862
if (props.suppressHydrationWarning !== true) {
28662863
checkForUnmatchedText(domElement.textContent, children, shouldWarnDev);
28672864
}
2868-
if (!enableClientRenderFallbackOnTextMismatch) {
2869-
// We really should be patching this in the commit phase but since
2870-
// this only affects legacy mode hydration which is deprecated anyway
2871-
// we can get away with it.
2872-
// Host singletons get their children appended and don't use the text
2873-
// content mechanism.
2874-
if (tag !== 'body') {
2875-
domElement.textContent = (children: any);
2876-
}
2877-
}
28782865
}
28792866
}
28802867

packages/react-dom/src/__tests__/ReactDOMFizzServer-test.js

-2
Original file line numberDiff line numberDiff line change
@@ -4419,7 +4419,6 @@ describe('ReactDOMFizzServer', () => {
44194419
);
44204420
});
44214421

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

4508-
// @gate enableClientRenderFallbackOnTextMismatch
45094507
it('only warns once on hydration mismatch while within a suspense boundary', async () => {
45104508
const originalConsoleError = console.error;
45114509
const mockError = jest.fn();

packages/react-dom/src/__tests__/ReactDOMFizzSuppressHydrationWarning-test.js

-120
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,6 @@ describe('ReactDOMFizzServerHydrationWarning', () => {
130130
: children;
131131
}
132132

133-
// @gate enableClientRenderFallbackOnTextMismatch
134133
it('suppresses but does not fix text mismatches with suppressHydrationWarning', async () => {
135134
function App({isClient}) {
136135
return (
@@ -170,47 +169,6 @@ describe('ReactDOMFizzServerHydrationWarning', () => {
170169
);
171170
});
172171

173-
// @gate !enableClientRenderFallbackOnTextMismatch
174-
it('suppresses and fixes text mismatches with suppressHydrationWarning', async () => {
175-
function App({isClient}) {
176-
return (
177-
<div>
178-
<span suppressHydrationWarning={true}>
179-
{isClient ? 'Client Text' : 'Server Text'}
180-
</span>
181-
<span suppressHydrationWarning={true}>{isClient ? 2 : 1}</span>
182-
</div>
183-
);
184-
}
185-
await act(() => {
186-
const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
187-
<App isClient={false} />,
188-
);
189-
pipe(writable);
190-
});
191-
expect(getVisibleChildren(container)).toEqual(
192-
<div>
193-
<span>Server Text</span>
194-
<span>1</span>
195-
</div>,
196-
);
197-
ReactDOMClient.hydrateRoot(container, <App isClient={true} />, {
198-
onRecoverableError(error) {
199-
// Don't miss a hydration error. There should be none.
200-
Scheduler.log(error.message);
201-
},
202-
});
203-
await waitForAll([]);
204-
// The text mismatch should be *silently* fixed. Even in production.
205-
expect(getVisibleChildren(container)).toEqual(
206-
<div>
207-
<span>Client Text</span>
208-
<span>2</span>
209-
</div>,
210-
);
211-
});
212-
213-
// @gate enableClientRenderFallbackOnTextMismatch
214172
it('suppresses but does not fix multiple text node mismatches with suppressHydrationWarning', async () => {
215173
function App({isClient}) {
216174
return (
@@ -252,48 +210,6 @@ describe('ReactDOMFizzServerHydrationWarning', () => {
252210
);
253211
});
254212

255-
// @gate !enableClientRenderFallbackOnTextMismatch
256-
it('suppresses and fixes multiple text node mismatches with suppressHydrationWarning', async () => {
257-
function App({isClient}) {
258-
return (
259-
<div>
260-
<span suppressHydrationWarning={true}>
261-
{isClient ? 'Client1' : 'Server1'}
262-
{isClient ? 'Client2' : 'Server2'}
263-
</span>
264-
</div>
265-
);
266-
}
267-
await act(() => {
268-
const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
269-
<App isClient={false} />,
270-
);
271-
pipe(writable);
272-
});
273-
expect(getVisibleChildren(container)).toEqual(
274-
<div>
275-
<span>
276-
{'Server1'}
277-
{'Server2'}
278-
</span>
279-
</div>,
280-
);
281-
ReactDOMClient.hydrateRoot(container, <App isClient={true} />, {
282-
onRecoverableError(error) {
283-
Scheduler.log(error.message);
284-
},
285-
});
286-
await waitForAll([]);
287-
expect(getVisibleChildren(container)).toEqual(
288-
<div>
289-
<span>
290-
{'Client1'}
291-
{'Client2'}
292-
</span>
293-
</div>,
294-
);
295-
});
296-
297213
it('errors on text-to-element mismatches with suppressHydrationWarning', async () => {
298214
function App({isClient}) {
299215
return (
@@ -345,7 +261,6 @@ describe('ReactDOMFizzServerHydrationWarning', () => {
345261
);
346262
});
347263

348-
// @gate enableClientRenderFallbackOnTextMismatch
349264
it('suppresses but does not fix client-only single text node mismatches with suppressHydrationWarning', async () => {
350265
function App({text}) {
351266
return (
@@ -386,41 +301,6 @@ describe('ReactDOMFizzServerHydrationWarning', () => {
386301
);
387302
});
388303

389-
// @gate !enableClientRenderFallbackOnTextMismatch
390-
it('suppresses and fixes client-only single text node mismatches with suppressHydrationWarning', async () => {
391-
function App({isClient}) {
392-
return (
393-
<div>
394-
<span suppressHydrationWarning={true}>
395-
{isClient ? 'Client' : null}
396-
</span>
397-
</div>
398-
);
399-
}
400-
await act(() => {
401-
const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
402-
<App isClient={false} />,
403-
);
404-
pipe(writable);
405-
});
406-
expect(getVisibleChildren(container)).toEqual(
407-
<div>
408-
<span />
409-
</div>,
410-
);
411-
ReactDOMClient.hydrateRoot(container, <App isClient={true} />, {
412-
onRecoverableError(error) {
413-
Scheduler.log(error.message);
414-
},
415-
});
416-
await waitForAll([]);
417-
expect(getVisibleChildren(container)).toEqual(
418-
<div>
419-
<span>{'Client'}</span>
420-
</div>,
421-
);
422-
});
423-
424304
// TODO: This behavior is not consistent with client-only single text node.
425305

426306
it('errors on server-only single text node mismatches with suppressHydrationWarning', async () => {

packages/react-dom/src/__tests__/ReactDOMFloat-test.js

-2
Original file line numberDiff line numberDiff line change
@@ -6446,7 +6446,6 @@ body {
64466446
);
64476447
});
64486448

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

8235-
// @gate enableClientRenderFallbackOnTextMismatch || !__DEV__
82368234
it('can render a title before a singleton even if that singleton clears its contents', async () => {
82378235
await act(() => {
82388236
const {pipe} = renderToPipeableStream(

packages/react-dom/src/__tests__/ReactDOMHydrationDiff-test.js

+31-62
Original file line numberDiff line numberDiff line change
@@ -80,28 +80,17 @@ describe('ReactDOMServerHydration', () => {
8080
</div>
8181
);
8282
}
83-
if (gate(flags => flags.enableClientRenderFallbackOnTextMismatch)) {
84-
expect(testMismatch(Mismatch)).toMatchInlineSnapshot(`
85-
[
86-
"Warning: Text content did not match. Server: "server" Client: "client"
87-
in main (at **)
88-
in div (at **)
89-
in Mismatch (at **)",
90-
"Warning: An error occurred during hydration. The server HTML was replaced with client content in <div>.",
91-
"Caught [Text content does not match server-rendered HTML.]",
92-
"Caught [There was an error while hydrating. Because the error happened outside of a Suspense boundary, the entire root will switch to client rendering.]",
93-
]
94-
`);
95-
} else {
96-
expect(testMismatch(Mismatch)).toMatchInlineSnapshot(`
97-
[
98-
"Warning: Text content did not match. Server: "server" Client: "client"
99-
in main (at **)
100-
in div (at **)
101-
in Mismatch (at **)",
102-
]
103-
`);
104-
}
83+
expect(testMismatch(Mismatch)).toMatchInlineSnapshot(`
84+
[
85+
"Warning: Text content did not match. Server: "server" Client: "client"
86+
in main (at **)
87+
in div (at **)
88+
in Mismatch (at **)",
89+
"Warning: An error occurred during hydration. The server HTML was replaced with client content in <div>.",
90+
"Caught [Text content does not match server-rendered HTML.]",
91+
"Caught [There was an error while hydrating. Because the error happened outside of a Suspense boundary, the entire root will switch to client rendering.]",
92+
]
93+
`);
10594
});
10695

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

120109
/* eslint-disable no-irregular-whitespace */
121-
if (gate(flags => flags.enableClientRenderFallbackOnTextMismatch)) {
122-
expect(testMismatch(Mismatch)).toMatchInlineSnapshot(`
123-
[
124-
"Warning: Text content did not match. Server: "This markup contains an nbsp entity:   server text" Client: "This markup contains an nbsp entity:   client text"
125-
in div (at **)
126-
in Mismatch (at **)",
127-
"Warning: An error occurred during hydration. The server HTML was replaced with client content in <div>.",
128-
"Caught [Text content does not match server-rendered HTML.]",
129-
"Caught [There was an error while hydrating. Because the error happened outside of a Suspense boundary, the entire root will switch to client rendering.]",
130-
]
131-
`);
132-
} else {
133-
expect(testMismatch(Mismatch)).toMatchInlineSnapshot(`
134-
[
135-
"Warning: Text content did not match. Server: "This markup contains an nbsp entity:   server text" Client: "This markup contains an nbsp entity:   client text"
136-
in div (at **)
137-
in Mismatch (at **)",
138-
]
139-
`);
140-
}
110+
expect(testMismatch(Mismatch)).toMatchInlineSnapshot(`
111+
[
112+
"Warning: Text content did not match. Server: "This markup contains an nbsp entity:   server text" Client: "This markup contains an nbsp entity:   client text"
113+
in div (at **)
114+
in Mismatch (at **)",
115+
"Warning: An error occurred during hydration. The server HTML was replaced with client content in <div>.",
116+
"Caught [Text content does not match server-rendered HTML.]",
117+
"Caught [There was an error while hydrating. Because the error happened outside of a Suspense boundary, the entire root will switch to client rendering.]",
118+
]
119+
`);
141120
/* eslint-enable no-irregular-whitespace */
142121
});
143122

@@ -388,26 +367,16 @@ describe('ReactDOMServerHydration', () => {
388367
function Mismatch({isClient}) {
389368
return <div className="parent">{isClient && 'only'}</div>;
390369
}
391-
if (gate(flags => flags.enableClientRenderFallbackOnTextMismatch)) {
392-
expect(testMismatch(Mismatch)).toMatchInlineSnapshot(`
393-
[
394-
"Warning: Text content did not match. Server: "" Client: "only"
395-
in div (at **)
396-
in Mismatch (at **)",
397-
"Warning: An error occurred during hydration. The server HTML was replaced with client content in <div>.",
398-
"Caught [Text content does not match server-rendered HTML.]",
399-
"Caught [There was an error while hydrating. Because the error happened outside of a Suspense boundary, the entire root will switch to client rendering.]",
400-
]
401-
`);
402-
} else {
403-
expect(testMismatch(Mismatch)).toMatchInlineSnapshot(`
404-
[
405-
"Warning: Text content did not match. Server: "" Client: "only"
406-
in div (at **)
407-
in Mismatch (at **)",
408-
]
409-
`);
410-
}
370+
expect(testMismatch(Mismatch)).toMatchInlineSnapshot(`
371+
[
372+
"Warning: Text content did not match. Server: "" Client: "only"
373+
in div (at **)
374+
in Mismatch (at **)",
375+
"Warning: An error occurred during hydration. The server HTML was replaced with client content in <div>.",
376+
"Caught [Text content does not match server-rendered HTML.]",
377+
"Caught [There was an error while hydrating. Because the error happened outside of a Suspense boundary, the entire root will switch to client rendering.]",
378+
]
379+
`);
411380
});
412381

413382
// @gate __DEV__

packages/react-dom/src/__tests__/ReactDOMServerIntegrationElements-test.js

+6-10
Original file line numberDiff line numberDiff line change
@@ -18,15 +18,13 @@ let React;
1818
let ReactDOM;
1919
let ReactDOMClient;
2020
let ReactDOMServer;
21-
let ReactFeatureFlags;
2221

2322
function initModules() {
2423
jest.resetModules();
2524
React = require('react');
2625
ReactDOM = require('react-dom');
2726
ReactDOMClient = require('react-dom/client');
2827
ReactDOMServer = require('react-dom/server');
29-
ReactFeatureFlags = require('shared/ReactFeatureFlags');
3028

3129
// Make them available to the helpers.
3230
return {
@@ -843,16 +841,15 @@ describe('ReactDOMServerIntegration', () => {
843841
if (
844842
render === serverRender ||
845843
render === streamRender ||
846-
(render === clientRenderOnServerString &&
847-
ReactFeatureFlags.enableClientRenderFallbackOnTextMismatch)
844+
render === clientRenderOnServerString
848845
) {
849846
expect(e.childNodes.length).toBe(1);
850-
// Everything becomes LF when parsed from server HTML or hydrated if enableClientRenderFallbackOnTextMismatch is on.
847+
// Everything becomes LF when parsed from server HTML or hydrated.
851848
// Null character is ignored.
852849
expectNode(e.childNodes[0], TEXT_NODE_TYPE, 'foo\nbar\nbaz\nqux');
853850
} else {
854851
expect(e.childNodes.length).toBe(1);
855-
// Client rendering (or hydration without enableClientRenderFallbackOnTextMismatch) uses JS value with CR.
852+
// Client rendering uses JS value with CR.
856853
// Null character stays.
857854

858855
expectNode(
@@ -876,19 +873,18 @@ describe('ReactDOMServerIntegration', () => {
876873
if (
877874
render === serverRender ||
878875
render === streamRender ||
879-
(render === clientRenderOnServerString &&
880-
ReactFeatureFlags.enableClientRenderFallbackOnTextMismatch)
876+
render === clientRenderOnServerString
881877
) {
882878
// We have three nodes because there is a comment between them.
883879
expect(e.childNodes.length).toBe(3);
884-
// Everything becomes LF when parsed from server HTML or hydrated if enableClientRenderFallbackOnTextMismatch is on.
880+
// Everything becomes LF when parsed from server HTML or hydrated.
885881
// Null character is ignored.
886882
expectNode(e.childNodes[0], TEXT_NODE_TYPE, 'foo\nbar');
887883
expectNode(e.childNodes[2], TEXT_NODE_TYPE, '\nbaz\nqux');
888884
} else if (render === clientRenderOnServerString) {
889885
// We have three nodes because there is a comment between them.
890886
expect(e.childNodes.length).toBe(3);
891-
// Hydration without enableClientRenderFallbackOnTextMismatch uses JS value with CR and null character.
887+
// Hydration uses JS value with CR and null character.
892888

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

0 commit comments

Comments
 (0)