From 98de7f666bff62e3f3be2f6a70f81b5960c3a317 Mon Sep 17 00:00:00 2001 From: Dan Abramov Date: Mon, 4 Apr 2022 03:01:55 +0100 Subject: [PATCH] Fix suppressHydrationWarning not working in production --- .../src/__tests__/ReactDOMFizzServer-test.js | 92 +++++++++++++++++++ .../react-dom/src/client/ReactDOMComponent.js | 15 +-- .../src/client/ReactDOMHostConfig.js | 5 +- 3 files changed, 101 insertions(+), 11 deletions(-) diff --git a/packages/react-dom/src/__tests__/ReactDOMFizzServer-test.js b/packages/react-dom/src/__tests__/ReactDOMFizzServer-test.js index 295e99b26a69..d4687ab4d365 100644 --- a/packages/react-dom/src/__tests__/ReactDOMFizzServer-test.js +++ b/packages/react-dom/src/__tests__/ReactDOMFizzServer-test.js @@ -2811,4 +2811,96 @@ describe('ReactDOMFizzServer', () => { , ); }); + + // @gate experimental + it('suppresses and fixes text mismatches with suppressHydrationWarning', async () => { + function App({isClient}) { + return ( +
+ + {isClient ? 'Client Text' : 'Server Text'} + + {isClient ? 2 : 1} + + hello,{isClient ? 'client' : 'server'} + +
+ ); + } + await act(async () => { + const {pipe} = ReactDOMFizzServer.renderToPipeableStream( + , + ); + pipe(writable); + }); + expect(getVisibleChildren(container)).toEqual( +
+ Server Text + 1 + + {'hello,'} + {'server'} + +
, + ); + ReactDOMClient.hydrateRoot(container, , { + onRecoverableError(error) { + // Don't miss a hydration error. There should be none. + Scheduler.unstable_yieldValue(error.message); + }, + }); + expect(Scheduler).toFlushAndYield([]); + // The text mismatch should be *silently* fixed. Even in production. + // The attribute mismatch should be ignored and not fixed. + expect(getVisibleChildren(container)).toEqual( +
+ Client Text + 2 + + {'hello,'} + {'client'} + +
, + ); + }); + + // @gate experimental + it('suppresses and does not fix html mismatches with suppressHydrationWarning', async () => { + function App({isClient}) { + return ( +
+

+

+ ); + } + await act(async () => { + const {pipe} = ReactDOMFizzServer.renderToPipeableStream( + , + ); + pipe(writable); + }); + expect(getVisibleChildren(container)).toEqual( +
+

Server HTML

+
, + ); + ReactDOMClient.hydrateRoot(container, , { + onRecoverableError(error) { + Scheduler.unstable_yieldValue(error.message); + }, + }); + expect(Scheduler).toFlushAndYield([]); + expect(getVisibleChildren(container)).toEqual( +
+

Server HTML

+
, + ); + }); }); diff --git a/packages/react-dom/src/client/ReactDOMComponent.js b/packages/react-dom/src/client/ReactDOMComponent.js index 7f9b6ac47d8a..75e017a45183 100644 --- a/packages/react-dom/src/client/ReactDOMComponent.js +++ b/packages/react-dom/src/client/ReactDOMComponent.js @@ -91,7 +91,6 @@ const STYLE = 'style'; const HTML = '__html'; let warnedUnknownTags; -let suppressHydrationWarning; let validatePropertiesInDevelopment; let warnForPropDifference; @@ -875,7 +874,6 @@ export function diffHydratedProperties( let extraAttributeNames: Set; if (__DEV__) { - suppressHydrationWarning = rawProps[SUPPRESS_HYDRATION_WARNING] === true; isCustomComponentTag = isCustomComponent(tag, rawProps); validatePropertiesInDevelopment(tag, rawProps); } @@ -984,7 +982,7 @@ export function diffHydratedProperties( // TODO: Should we use domElement.firstChild.nodeValue to compare? if (typeof nextProp === 'string') { if (domElement.textContent !== nextProp) { - if (!suppressHydrationWarning) { + if (rawProps[SUPPRESS_HYDRATION_WARNING] !== true) { checkForUnmatchedText( domElement.textContent, nextProp, @@ -996,7 +994,7 @@ export function diffHydratedProperties( } } else if (typeof nextProp === 'number') { if (domElement.textContent !== '' + nextProp) { - if (!suppressHydrationWarning) { + if (rawProps[SUPPRESS_HYDRATION_WARNING] !== true) { checkForUnmatchedText( domElement.textContent, nextProp, @@ -1028,7 +1026,7 @@ export function diffHydratedProperties( isCustomComponentTag && enableCustomElementPropertySupport ? null : getPropertyInfo(propKey); - if (suppressHydrationWarning) { + if (rawProps[SUPPRESS_HYDRATION_WARNING] === true) { // Don't bother comparing. We're ignoring all these warnings. } else if ( propKey === SUPPRESS_CONTENT_EDITABLE_WARNING || @@ -1150,8 +1148,11 @@ export function diffHydratedProperties( if (__DEV__) { if (shouldWarnDev) { - // $FlowFixMe - Should be inferred as not undefined. - if (extraAttributeNames.size > 0 && !suppressHydrationWarning) { + if ( + // $FlowFixMe - Should be inferred as not undefined. + extraAttributeNames.size > 0 && + rawProps[SUPPRESS_HYDRATION_WARNING] !== true + ) { // $FlowFixMe - Should be inferred as not undefined. warnForExtraAttributes(extraAttributeNames); } diff --git a/packages/react-dom/src/client/ReactDOMHostConfig.js b/packages/react-dom/src/client/ReactDOMHostConfig.js index 8ded833556c9..925f7088e4b0 100644 --- a/packages/react-dom/src/client/ReactDOMHostConfig.js +++ b/packages/react-dom/src/client/ReactDOMHostConfig.js @@ -132,10 +132,7 @@ type SelectionInformation = {| selectionRange: mixed, |}; -let SUPPRESS_HYDRATION_WARNING; -if (__DEV__) { - SUPPRESS_HYDRATION_WARNING = 'suppressHydrationWarning'; -} +const SUPPRESS_HYDRATION_WARNING = 'suppressHydrationWarning'; const SUSPENSE_START_DATA = '$'; const SUSPENSE_END_DATA = '/$';