Don't mute hydration errors forcing client render (#24276)
* Don't mute hydration errors forcing client render * Nits
dan committed
Apr 5, 2022 at 02:11 UTC
9ededef945f032fa6257711d45a4f198bfdf1633
4 files changed
+99
-33
packages/react-dom/src/__tests__/ReactDOMFizzSuppressHydrationWarning-test.js
+14
-7
@@ -248,9 +248,10 @@ describe('ReactDOMFizzServerHydrationWarning', () => {
248
]);
249
}).toErrorDev(
250
[
251
+ 'Expected server HTML to contain a matching <span> in <span>',
252
'An error occurred during hydration. The server HTML was replaced with client content in <div>.',
253
],
253
- {withoutStack: true},
254
+ {withoutStack: 1},
255
);
256
} else {
257
// This used to not warn.
@@ -336,9 +337,10 @@ describe('ReactDOMFizzServerHydrationWarning', () => {
337
]);
338
}).toErrorDev(
339
[
340
+ 'Did not expect server HTML to contain the text node "Server" in <span>',
341
'An error occurred during hydration. The server HTML was replaced with client content in <div>.',
342
],
341
- {withoutStack: true},
343
+ {withoutStack: 1},
344
);
345
} else {
346
// This used to not warn.
@@ -389,9 +391,10 @@ describe('ReactDOMFizzServerHydrationWarning', () => {
391
]);
392
}).toErrorDev(
393
[
394
+ 'Expected server HTML to contain a matching text node for "Client" in <span>.',
395
'An error occurred during hydration. The server HTML was replaced with client content in <div>.',
396
],
394
- {withoutStack: true},
397
+ {withoutStack: 1},
398
);
399
} else {
400
// This used to not warn.
@@ -445,9 +448,10 @@ describe('ReactDOMFizzServerHydrationWarning', () => {
448
]);
449
}).toErrorDev(
450
[
451
+ 'Did not expect server HTML to contain the text node "Server" in <span>.',
452
'An error occurred during hydration. The server HTML was replaced with client content in <div>.',
453
],
450
- {withoutStack: true},
454
+ {withoutStack: 1},
455
);
456
} else {
457
// This used to not warn.
@@ -500,9 +504,10 @@ describe('ReactDOMFizzServerHydrationWarning', () => {
504
]);
505
}).toErrorDev(
506
[
507
+ 'Expected server HTML to contain a matching text node for "Client" in <span>.',
508
'An error occurred during hydration. The server HTML was replaced with client content in <div>.',
509
],
505
- {withoutStack: true},
510
+ {withoutStack: 1},
511
);
512
} else {
513
// This used to not warn.
@@ -630,9 +635,10 @@ describe('ReactDOMFizzServerHydrationWarning', () => {
635
]);
636
}).toErrorDev(
637
[
638
+ 'Expected server HTML to contain a matching <p> in <div>.',
639
'An error occurred during hydration. The server HTML was replaced with client content in <div>.',
640
],
635
- {withoutStack: true},
641
+ {withoutStack: 1},
642
);
643
} else {
644
// This used to not warn.
@@ -681,9 +687,10 @@ describe('ReactDOMFizzServerHydrationWarning', () => {
687
]);
688
}).toErrorDev(
689
[
690
+ 'Did not expect server HTML to contain a <p> in <div>.',
691
'An error occurred during hydration. The server HTML was replaced with client content in <div>.',
692
],
686
- {withoutStack: true},
693
+ {withoutStack: 1},
694
);
695
} else {
696
// This used to not warn.
packages/react-dom/src/client/ReactDOMHostConfig.js
+31
-12
@@ -62,6 +62,7 @@ import dangerousStyleValue from '../shared/dangerousStyleValue';
62
import {retryIfBlockedOn} from '../events/ReactDOMEventReplaying';
63
64
import {
65
+ enableClientRenderFallbackOnHydrationMismatch,
66
enableSuspenseServerRenderer,
67
enableCreateEventHandleAPI,
68
enableScopeAPI,
@@ -1004,14 +1005,20 @@ export function didNotHydrateInstance(
1005
parentProps: Props,
1006
parentInstance: Instance,
1007
instance: HydratableInstance,
1008
+ isConcurrentMode: boolean,
1009
) {
1008
- if (__DEV__ && parentProps[SUPPRESS_HYDRATION_WARNING] !== true) {
1009
- if (instance.nodeType === ELEMENT_NODE) {
1010
- warnForDeletedHydratableElement(parentInstance, (instance: any));
1011
- } else if (instance.nodeType === COMMENT_NODE) {
1012
- // TODO: warnForDeletedHydratableSuspenseBoundary
1013
- } else {
1014
- warnForDeletedHydratableText(parentInstance, (instance: any));
1010
+ if (__DEV__) {
1011
+ if (
1012
+ (enableClientRenderFallbackOnHydrationMismatch && isConcurrentMode) ||
1013
+ parentProps[SUPPRESS_HYDRATION_WARNING] !== true
1014
+ ) {
1015
+ if (instance.nodeType === ELEMENT_NODE) {
1016
+ warnForDeletedHydratableElement(parentInstance, (instance: any));
1017
+ } else if (instance.nodeType === COMMENT_NODE) {
1018
+ // TODO: warnForDeletedHydratableSuspenseBoundary
1019
+ } else {
1020
+ warnForDeletedHydratableText(parentInstance, (instance: any));
1021
+ }
1022
}
1023
}
1024
}
@@ -1082,9 +1089,15 @@ export function didNotFindHydratableInstance(
1089
parentInstance: Instance,
1090
type: string,
1091
props: Props,
1092
+ isConcurrentMode: boolean,
1093
) {
1086
- if (__DEV__ && parentProps[SUPPRESS_HYDRATION_WARNING] !== true) {
1087
- warnForInsertedHydratedElement(parentInstance, type, props);
1094
+ if (__DEV__) {
1095
+ if (
1096
+ (enableClientRenderFallbackOnHydrationMismatch && isConcurrentMode) ||
1097
+ parentProps[SUPPRESS_HYDRATION_WARNING] !== true
1098
+ ) {
1099
+ warnForInsertedHydratedElement(parentInstance, type, props);
1100
+ }
1101
}
1102
}
1103
@@ -1093,9 +1106,15 @@ export function didNotFindHydratableTextInstance(
1106
parentProps: Props,
1107
parentInstance: Instance,
1108
text: string,
1109
+ isConcurrentMode: boolean,
1110
) {
1097
- if (__DEV__ && parentProps[SUPPRESS_HYDRATION_WARNING] !== true) {
1098
- warnForInsertedHydratedText(parentInstance, text);
1111
+ if (__DEV__) {
1112
+ if (
1113
+ (enableClientRenderFallbackOnHydrationMismatch && isConcurrentMode) ||
1114
+ parentProps[SUPPRESS_HYDRATION_WARNING] !== true
1115
+ ) {
1116
+ warnForInsertedHydratedText(parentInstance, text);
1117
+ }
1118
}
1119
}
1120
@@ -1104,7 +1123,7 @@ export function didNotFindHydratableSuspenseInstance(
1123
parentProps: Props,
1124
parentInstance: Instance,
1125
) {
1107
- if (__DEV__ && parentProps[SUPPRESS_HYDRATION_WARNING] !== true) {
1126
+ if (__DEV__) {
1127
// TODO: warnForInsertedHydratedSuspense(parentInstance);
1128
}
1129
}
packages/react-reconciler/src/ReactFiberHydrationContext.new.js
+27
-7
@@ -148,21 +148,26 @@ function warnUnhydratedInstance(
148
) {
149
if (__DEV__) {
150
switch (returnFiber.tag) {
151
- case HostRoot:
151
+ case HostRoot: {
152
didNotHydrateInstanceWithinContainer(
153
returnFiber.stateNode.containerInfo,
154
instance,
155
);
156
break;
157
- case HostComponent:
157
+ }
158
+ case HostComponent: {
159
+ const isConcurrentMode = (returnFiber.mode & ConcurrentMode) !== NoMode;
160
didNotHydrateInstance(
161
returnFiber.type,
162
returnFiber.memoizedProps,
163
returnFiber.stateNode,
164
instance,
165
+ // TODO: Delete this argument when we remove the legacy root API.
166
+ isConcurrentMode,
167
);
168
break;
165
- case SuspenseComponent:
169
+ }
170
+ case SuspenseComponent: {
171
const suspenseState: SuspenseState = returnFiber.memoizedState;
172
if (suspenseState.dehydrated !== null)
173
didNotHydrateInstanceWithinSuspenseInstance(
@@ -170,6 +175,7 @@ function warnUnhydratedInstance(
175
instance,
176
);
177
break;
178
+ }
179
}
180
}
181
}
@@ -234,33 +240,44 @@ function warnNonhydratedInstance(returnFiber: Fiber, fiber: Fiber) {
240
const parentProps = returnFiber.memoizedProps;
241
const parentInstance = returnFiber.stateNode;
242
switch (fiber.tag) {
237
- case HostComponent:
243
+ case HostComponent: {
244
const type = fiber.type;
245
const props = fiber.pendingProps;
246
+ const isConcurrentMode =
247
+ (returnFiber.mode & ConcurrentMode) !== NoMode;
248
didNotFindHydratableInstance(
249
parentType,
250
parentProps,
251
parentInstance,
252
type,
253
props,
254
+ // TODO: Delete this argument when we remove the legacy root API.
255
+ isConcurrentMode,
256
);
257
break;
248
- case HostText:
258
+ }
259
+ case HostText: {
260
const text = fiber.pendingProps;
261
+ const isConcurrentMode =
262
+ (returnFiber.mode & ConcurrentMode) !== NoMode;
263
didNotFindHydratableTextInstance(
264
parentType,
265
parentProps,
266
parentInstance,
267
text,
268
+ // TODO: Delete this argument when we remove the legacy root API.
269
+ isConcurrentMode,
270
);
271
break;
257
- case SuspenseComponent:
272
+ }
273
+ case SuspenseComponent: {
274
didNotFindHydratableSuspenseInstance(
275
parentType,
276
parentProps,
277
parentInstance,
278
);
279
break;
280
+ }
281
}
282
break;
283
}
@@ -476,10 +493,11 @@ function prepareToHydrateHostTextInstance(fiber: Fiber): boolean {
493
// hydration parent is the parent host component of this host text.
494
const returnFiber = hydrationParentFiber;
495
if (returnFiber !== null) {
479
- const isConcurrentMode = (returnFiber.mode & ConcurrentMode) !== NoMode;
496
switch (returnFiber.tag) {
497
case HostRoot: {
498
const parentContainer = returnFiber.stateNode.containerInfo;
499
+ const isConcurrentMode =
500
+ (returnFiber.mode & ConcurrentMode) !== NoMode;
501
didNotMatchHydratedContainerTextInstance(
502
parentContainer,
503
textInstance,
@@ -493,6 +511,8 @@ function prepareToHydrateHostTextInstance(fiber: Fiber): boolean {
511
const parentType = returnFiber.type;
512
const parentProps = returnFiber.memoizedProps;
513
const parentInstance = returnFiber.stateNode;
514
+ const isConcurrentMode =
515
+ (returnFiber.mode & ConcurrentMode) !== NoMode;
516
didNotMatchHydratedTextInstance(
517
parentType,
518
parentProps,
packages/react-reconciler/src/ReactFiberHydrationContext.old.js
+27
-7
@@ -148,21 +148,26 @@ function warnUnhydratedInstance(
148
) {
149
if (__DEV__) {
150
switch (returnFiber.tag) {
151
- case HostRoot:
151
+ case HostRoot: {
152
didNotHydrateInstanceWithinContainer(
153
returnFiber.stateNode.containerInfo,
154
instance,
155
);
156
break;
157
- case HostComponent:
157
+ }
158
+ case HostComponent: {
159
+ const isConcurrentMode = (returnFiber.mode & ConcurrentMode) !== NoMode;
160
didNotHydrateInstance(
161
returnFiber.type,
162
returnFiber.memoizedProps,
163
returnFiber.stateNode,
164
instance,
165
+ // TODO: Delete this argument when we remove the legacy root API.
166
+ isConcurrentMode,
167
);
168
break;
165
- case SuspenseComponent:
169
+ }
170
+ case SuspenseComponent: {
171
const suspenseState: SuspenseState = returnFiber.memoizedState;
172
if (suspenseState.dehydrated !== null)
173
didNotHydrateInstanceWithinSuspenseInstance(
@@ -170,6 +175,7 @@ function warnUnhydratedInstance(
175
instance,
176
);
177
break;
178
+ }
179
}
180
}
181
}
@@ -234,33 +240,44 @@ function warnNonhydratedInstance(returnFiber: Fiber, fiber: Fiber) {
240
const parentProps = returnFiber.memoizedProps;
241
const parentInstance = returnFiber.stateNode;
242
switch (fiber.tag) {
237
- case HostComponent:
243
+ case HostComponent: {
244
const type = fiber.type;
245
const props = fiber.pendingProps;
246
+ const isConcurrentMode =
247
+ (returnFiber.mode & ConcurrentMode) !== NoMode;
248
didNotFindHydratableInstance(
249
parentType,
250
parentProps,
251
parentInstance,
252
type,
253
props,
254
+ // TODO: Delete this argument when we remove the legacy root API.
255
+ isConcurrentMode,
256
);
257
break;
248
- case HostText:
258
+ }
259
+ case HostText: {
260
const text = fiber.pendingProps;
261
+ const isConcurrentMode =
262
+ (returnFiber.mode & ConcurrentMode) !== NoMode;
263
didNotFindHydratableTextInstance(
264
parentType,
265
parentProps,
266
parentInstance,
267
text,
268
+ // TODO: Delete this argument when we remove the legacy root API.
269
+ isConcurrentMode,
270
);
271
break;
257
- case SuspenseComponent:
272
+ }
273
+ case SuspenseComponent: {
274
didNotFindHydratableSuspenseInstance(
275
parentType,
276
parentProps,
277
parentInstance,
278
);
279
break;
280
+ }
281
}
282
break;
283
}
@@ -476,10 +493,11 @@ function prepareToHydrateHostTextInstance(fiber: Fiber): boolean {
493
// hydration parent is the parent host component of this host text.
494
const returnFiber = hydrationParentFiber;
495
if (returnFiber !== null) {
479
- const isConcurrentMode = (returnFiber.mode & ConcurrentMode) !== NoMode;
496
switch (returnFiber.tag) {
497
case HostRoot: {
498
const parentContainer = returnFiber.stateNode.containerInfo;
499
+ const isConcurrentMode =
500
+ (returnFiber.mode & ConcurrentMode) !== NoMode;
501
didNotMatchHydratedContainerTextInstance(
502
parentContainer,
503
textInstance,
@@ -493,6 +511,8 @@ function prepareToHydrateHostTextInstance(fiber: Fiber): boolean {
511
const parentType = returnFiber.type;
512
const parentProps = returnFiber.memoizedProps;
513
const parentInstance = returnFiber.stateNode;
514
+ const isConcurrentMode =
515
+ (returnFiber.mode & ConcurrentMode) !== NoMode;
516
didNotMatchHydratedTextInstance(
517
parentType,
518
parentProps,