Skip to content

Commit afb3d51

Browse files
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>
1 parent 8e17bfd commit afb3d51

File tree

4 files changed

+129
-5
lines changed

4 files changed

+129
-5
lines changed

Diff for: packages/react-dom-bindings/src/client/ReactDOMComponent.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -1440,7 +1440,7 @@ export function diffHydratedProperties(
14401440
shouldWarnDev,
14411441
);
14421442
}
1443-
if (!isConcurrentMode) {
1443+
if (!isConcurrentMode || !enableClientRenderFallbackOnTextMismatch) {
14441444
updatePayload = ['children', children];
14451445
}
14461446
}

Diff for: packages/react-dom/src/__tests__/ReactDOMFizzSuppressHydrationWarning-test.js

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

133+
// @gate enableClientRenderFallbackOnTextMismatch
133134
it('suppresses but does not fix text mismatches with suppressHydrationWarning', async () => {
134135
function App({isClient}) {
135136
return (
@@ -169,6 +170,47 @@ describe('ReactDOMFizzServerHydrationWarning', () => {
169170
);
170171
});
171172

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
172214
it('suppresses but does not fix multiple text node mismatches with suppressHydrationWarning', async () => {
173215
function App({isClient}) {
174216
return (
@@ -210,6 +252,48 @@ describe('ReactDOMFizzServerHydrationWarning', () => {
210252
);
211253
});
212254

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+
213297
it('errors on text-to-element mismatches with suppressHydrationWarning', async () => {
214298
function App({isClient}) {
215299
return (
@@ -261,6 +345,7 @@ describe('ReactDOMFizzServerHydrationWarning', () => {
261345
);
262346
});
263347

348+
// @gate enableClientRenderFallbackOnTextMismatch
264349
it('suppresses but does not fix client-only single text node mismatches with suppressHydrationWarning', async () => {
265350
function App({text}) {
266351
return (
@@ -301,6 +386,41 @@ describe('ReactDOMFizzServerHydrationWarning', () => {
301386
);
302387
});
303388

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+
304424
// TODO: This behavior is not consistent with client-only single text node.
305425

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

Diff for: packages/react-dom/src/__tests__/ReactDOMFloat-test.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -5617,7 +5617,7 @@ background-color: green;
56175617
]);
56185618
});
56195619

5620-
// @gate enableFloat && enableHostSingletons && enableClientRenderFallbackOnTextMismatch
5620+
// @gate enableFloat && enableHostSingletons && (enableClientRenderFallbackOnTextMismatch || !__DEV__)
56215621
it('can render a title before a singleton even if that singleton clears its contents', async () => {
56225622
await actIntoEmptyDocument(() => {
56235623
const {pipe} = renderToPipeableStream(

Diff for: packages/react-reconciler/src/ReactFiberHydrationContext.js

+7-3
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,11 @@ import {
3535
NoFlags,
3636
DidCapture,
3737
} from './ReactFiberFlags';
38-
import {enableHostSingletons, enableFloat} from 'shared/ReactFeatureFlags';
38+
import {
39+
enableHostSingletons,
40+
enableFloat,
41+
enableClientRenderFallbackOnTextMismatch,
42+
} from 'shared/ReactFeatureFlags';
3943

4044
import {
4145
createFiberFromHostInstanceForDeletion,
@@ -728,7 +732,7 @@ function prepareToHydrateHostTextInstance(fiber: Fiber): boolean {
728732
isConcurrentMode,
729733
shouldWarnIfMismatchDev,
730734
);
731-
if (isConcurrentMode) {
735+
if (isConcurrentMode && enableClientRenderFallbackOnTextMismatch) {
732736
// In concurrent mode we never update the mismatched text,
733737
// even if the error was ignored.
734738
return false;
@@ -752,7 +756,7 @@ function prepareToHydrateHostTextInstance(fiber: Fiber): boolean {
752756
isConcurrentMode,
753757
shouldWarnIfMismatchDev,
754758
);
755-
if (isConcurrentMode) {
759+
if (isConcurrentMode && enableClientRenderFallbackOnTextMismatch) {
756760
// In concurrent mode we never update the mismatched text,
757761
// even if the error was ignored.
758762
return false;

0 commit comments

Comments
 (0)