@@ -72,6 +72,7 @@ import {validateProperties as validateUnknownProperties} from '../shared/ReactDO
72
72
import {
73
73
enableTrustedTypesIntegration ,
74
74
enableCustomElementPropertySupport ,
75
+ enableClientRenderFallbackOnHydrationMismatch ,
75
76
} from 'shared/ReactFeatureFlags' ;
76
77
import {
77
78
mediaEventTypes ,
@@ -93,13 +94,11 @@ let warnedUnknownTags;
93
94
let suppressHydrationWarning ;
94
95
95
96
let validatePropertiesInDevelopment ;
96
- let warnForTextDifference ;
97
97
let warnForPropDifference ;
98
98
let warnForExtraAttributes ;
99
99
let warnForInvalidEventListener ;
100
100
let canDiffStyleForHydrationWarning ;
101
101
102
- let normalizeMarkupForTextOrAttribute ;
103
102
let normalizeHTML ;
104
103
105
104
if ( __DEV__ ) {
@@ -133,45 +132,6 @@ if (__DEV__) {
133
132
// See https://github.com/facebook/react/issues/11807
134
133
canDiffStyleForHydrationWarning = canUseDOM && ! document . documentMode ;
135
134
136
- // HTML parsing normalizes CR and CRLF to LF.
137
- // It also can turn \u0000 into \uFFFD inside attributes.
138
- // https://www.w3.org/TR/html5/single-page.html#preprocessing-the-input-stream
139
- // If we have a mismatch, it might be caused by that.
140
- // We will still patch up in this case but not fire the warning.
141
- const NORMALIZE_NEWLINES_REGEX = / \r \n ? / g;
142
- const NORMALIZE_NULL_AND_REPLACEMENT_REGEX = / \u0000 | \uFFFD / g;
143
-
144
- normalizeMarkupForTextOrAttribute = function ( markup : mixed ) : string {
145
- if ( __DEV__ ) {
146
- checkHtmlStringCoercion ( markup ) ;
147
- }
148
- const markupString =
149
- typeof markup === 'string' ? markup : '' + ( markup : any ) ;
150
- return markupString
151
- . replace ( NORMALIZE_NEWLINES_REGEX , '\n' )
152
- . replace ( NORMALIZE_NULL_AND_REPLACEMENT_REGEX , '' ) ;
153
- } ;
154
-
155
- warnForTextDifference = function (
156
- serverText : string ,
157
- clientText : string | number ,
158
- ) {
159
- if ( didWarnInvalidHydration ) {
160
- return ;
161
- }
162
- const normalizedClientText = normalizeMarkupForTextOrAttribute ( clientText ) ;
163
- const normalizedServerText = normalizeMarkupForTextOrAttribute ( serverText ) ;
164
- if ( normalizedServerText === normalizedClientText ) {
165
- return ;
166
- }
167
- didWarnInvalidHydration = true ;
168
- console . error (
169
- 'Text content did not match. Server: "%s" Client: "%s"' ,
170
- normalizedServerText ,
171
- normalizedClientText ,
172
- ) ;
173
- } ;
174
-
175
135
warnForPropDifference = function (
176
136
propName : string ,
177
137
serverValue : mixed ,
@@ -248,6 +208,53 @@ if (__DEV__) {
248
208
} ;
249
209
}
250
210
211
+ // HTML parsing normalizes CR and CRLF to LF.
212
+ // It also can turn \u0000 into \uFFFD inside attributes.
213
+ // https://www.w3.org/TR/html5/single-page.html#preprocessing-the-input-stream
214
+ // If we have a mismatch, it might be caused by that.
215
+ // We will still patch up in this case but not fire the warning.
216
+ const NORMALIZE_NEWLINES_REGEX = / \r \n ? / g;
217
+ const NORMALIZE_NULL_AND_REPLACEMENT_REGEX = / \u0000 | \uFFFD / g;
218
+
219
+ function normalizeMarkupForTextOrAttribute ( markup : mixed ) : string {
220
+ if ( __DEV__ ) {
221
+ checkHtmlStringCoercion ( markup ) ;
222
+ }
223
+ const markupString = typeof markup === 'string' ? markup : '' + ( markup : any ) ;
224
+ return markupString
225
+ . replace ( NORMALIZE_NEWLINES_REGEX , '\n' )
226
+ . replace ( NORMALIZE_NULL_AND_REPLACEMENT_REGEX , '' ) ;
227
+ }
228
+
229
+ export function checkForUnmatchedText (
230
+ serverText : string ,
231
+ clientText : string | number ,
232
+ isConcurrentMode : boolean ,
233
+ ) {
234
+ const normalizedClientText = normalizeMarkupForTextOrAttribute ( clientText ) ;
235
+ const normalizedServerText = normalizeMarkupForTextOrAttribute ( serverText ) ;
236
+ if ( normalizedServerText === normalizedClientText ) {
237
+ return ;
238
+ }
239
+
240
+ if ( __DEV__ ) {
241
+ if ( ! didWarnInvalidHydration ) {
242
+ didWarnInvalidHydration = true ;
243
+ console . error (
244
+ 'Text content did not match. Server: "%s" Client: "%s"' ,
245
+ normalizedServerText ,
246
+ normalizedClientText ,
247
+ ) ;
248
+ }
249
+ }
250
+
251
+ if ( isConcurrentMode && enableClientRenderFallbackOnHydrationMismatch ) {
252
+ // In concurrent roots, we throw when there's a text mismatch and revert to
253
+ // client rendering, up to the nearest Suspense boundary.
254
+ throw new Error ( 'Text content does not match server-rendered HTML.' ) ;
255
+ }
256
+ }
257
+
251
258
function getOwnerDocumentFromRootContainer (
252
259
rootContainerElement : Element | Document ,
253
260
) : Document {
@@ -858,6 +865,7 @@ export function diffHydratedProperties(
858
865
rawProps : Object ,
859
866
parentNamespace : string ,
860
867
rootContainerElement : Element | Document ,
868
+ isConcurrentMode : boolean ,
861
869
) : null | Array < mixed > {
862
870
let isCustomComponentTag ;
863
871
let extraAttributeNames : Set < string > ;
@@ -972,15 +980,23 @@ export function diffHydratedProperties(
972
980
// TODO: Should we use domElement.firstChild.nodeValue to compare?
973
981
if ( typeof nextProp === 'string' ) {
974
982
if ( domElement . textContent !== nextProp ) {
975
- if ( __DEV__ && ! suppressHydrationWarning ) {
976
- warnForTextDifference ( domElement . textContent , nextProp ) ;
983
+ if ( ! suppressHydrationWarning ) {
984
+ checkForUnmatchedText (
985
+ domElement . textContent ,
986
+ nextProp ,
987
+ isConcurrentMode ,
988
+ ) ;
977
989
}
978
990
updatePayload = [ CHILDREN , nextProp ] ;
979
991
}
980
992
} else if ( typeof nextProp === 'number' ) {
981
993
if ( domElement . textContent !== '' + nextProp ) {
982
- if ( __DEV__ && ! suppressHydrationWarning ) {
983
- warnForTextDifference ( domElement . textContent , nextProp ) ;
994
+ if ( ! suppressHydrationWarning ) {
995
+ checkForUnmatchedText (
996
+ domElement . textContent ,
997
+ nextProp ,
998
+ isConcurrentMode ,
999
+ ) ;
984
1000
}
985
1001
updatePayload = [ CHILDREN , '' + nextProp ] ;
986
1002
}
@@ -1165,17 +1181,15 @@ export function diffHydratedProperties(
1165
1181
return updatePayload ;
1166
1182
}
1167
1183
1168
- export function diffHydratedText ( textNode : Text , text : string ) : boolean {
1184
+ export function diffHydratedText (
1185
+ textNode : Text ,
1186
+ text : string ,
1187
+ isConcurrentMode : boolean ,
1188
+ ) : boolean {
1169
1189
const isDifferent = textNode . nodeValue !== text ;
1170
1190
return isDifferent ;
1171
1191
}
1172
1192
1173
- export function warnForUnmatchedText ( textNode : Text , text : string ) {
1174
- if ( __DEV__ ) {
1175
- warnForTextDifference ( textNode . nodeValue , text ) ;
1176
- }
1177
- }
1178
-
1179
1193
export function warnForDeletedHydratableElement (
1180
1194
parentNode : Element | Document ,
1181
1195
child : Element ,
0 commit comments