Allow forms to skip hydration of hidden inputs (#26735)
This allows us to emit extra ephemeral data that will only be used on server rendered forms. First I refactored the shouldSkip functions to now just do that work inside the canHydrate methods. This makes the Config bindings a little less surface area but it also helps us optimize a bit since we now can look at the code together and find shared paths. canHydrate returns the instance if it matches, that used to just be there to refine the type but it can also be used to just return a different instance later that we find. If we don't find one, we'll bail out and error regardless so no need to skip past anything.
Sebastian Markbåge committed
May 1, 2023 at 15:35 UTC
67f4fb02130b1fe1856289e3b66bb0b8cca57ff7
5 files changed
+202
-209
packages/react-dom-bindings/src/client/ReactFiberConfigDOM.js
+153
-128
@@ -89,6 +89,7 @@ import {
89
enableHostSingletons,
90
enableTrustedTypesIntegration,
91
diffInCommitPhase,
92
+ enableFormActions,
93
} from 'shared/ReactFeatureFlags';
94
import {
95
HostComponent,
@@ -1038,150 +1039,164 @@ export function isHydratableText(text: string): boolean {
1039
return text !== '';
1040
}
1041
1041
-export function shouldSkipHydratableForInstance(
1042
+export function canHydrateInstance(
1043
instance: HydratableInstance,
1044
type: string,
1045
props: Props,
1045
-): boolean {
1046
- if (instance.nodeType !== ELEMENT_NODE) {
1047
- // This is a suspense boundary or Text node.
1048
- // Suspense Boundaries are never expected to be injected by 3rd parties. If we see one it should be matched
1049
- // and this is a hydration error.
1050
- // Text Nodes are also not expected to be injected by 3rd parties. This is less of a guarantee for <body>
1051
- // but it seems reasonable and conservative to reject this as a hydration error as well
1052
- return false;
1053
- } else if (
1054
- instance.nodeName.toLowerCase() !== type.toLowerCase() ||
1055
- isMarkedHoistable(instance)
1056
- ) {
1057
- // We are either about to
1058
- return true;
1059
- } else {
1060
- // We have an Element with the right type.
1046
+ inRootOrSingleton: boolean,
1047
+): null | Instance {
1048
+ while (instance.nodeType === ELEMENT_NODE) {
1049
const element: Element = (instance: any);
1050
const anyProps = (props: any);
1063
-
1064
- // We are going to try to exclude it if we can definitely identify it as a hoisted Node or if
1065
- // we can guess that the node is likely hoisted or was inserted by a 3rd party script or browser extension
1066
- // using high entropy attributes for certain types. This technique will fail for strange insertions like
1067
- // extension prepending <div> in the <body> but that already breaks before and that is an edge case.
1068
- switch (type) {
1069
- // case 'title':
1070
- //We assume all titles are matchable. You should only have one in the Document, at least in a hoistable scope
1071
- // and if you are a HostComponent with type title we must either be in an <svg> context or this title must have an `itemProp` prop.
1072
- case 'meta': {
1073
- // The only way to opt out of hoisting meta tags is to give it an itemprop attribute. We assume there will be
1074
- // not 3rd party meta tags that are prepended, accepting the cases where this isn't true because meta tags
1075
- // are usually only functional for SSR so even in a rare case where we did bind to an injected tag the runtime
1076
- // implications are minimal
1077
- if (!element.hasAttribute('itemprop')) {
1078
- // This is a Hoistable
1079
- return true;
1080
- }
1081
- break;
1082
- }
1083
- case 'link': {
1084
- // Links come in many forms and we do expect 3rd parties to inject them into <head> / <body>. We exclude known resources
1085
- // and then use high-entroy attributes like href which are almost always used and almost always unique to filter out unlikely
1086
- // matches.
1087
- const rel = element.getAttribute('rel');
1088
- if (rel === 'stylesheet' && element.hasAttribute('data-precedence')) {
1089
- // This is a stylesheet resource
1090
- return true;
1091
- } else if (
1092
- rel !== anyProps.rel ||
1093
- element.getAttribute('href') !==
1094
- (anyProps.href == null ? null : anyProps.href) ||
1095
- element.getAttribute('crossorigin') !==
1096
- (anyProps.crossOrigin == null ? null : anyProps.crossOrigin) ||
1097
- element.getAttribute('title') !==
1098
- (anyProps.title == null ? null : anyProps.title)
1051
+ if (element.nodeName.toLowerCase() !== type.toLowerCase()) {
1052
+ if (!inRootOrSingleton || !enableHostSingletons) {
1053
+ // Usually we error for mismatched tags.
1054
+ if (
1055
+ enableFormActions &&
1056
+ element.nodeName === 'INPUT' &&
1057
+ (element: any).type === 'hidden'
1058
) {
1100
- // rel + href should usually be enough to uniquely identify a link however crossOrigin can vary for rel preconnect
1101
- // and title could vary for rel alternate
1102
- return true;
1059
+ // If we have extra hidden inputs, we don't mismatch. This allows us to embed
1060
+ // extra form data in the original form.
1061
+ } else {
1062
+ return null;
1063
}
1104
- break;
1064
}
1106
- case 'style': {
1107
- // Styles are hard to match correctly. We can exclude known resources but otherwise we accept the fact that a non-hoisted style tags
1108
- // in <head> or <body> are likely never going to be unmounted given their position in the document and the fact they likely hold global styles
1109
- if (element.hasAttribute('data-precedence')) {
1110
- // This is a style resource
1111
- return true;
1112
- }
1113
- break;
1065
+ // In root or singleton parents we skip past mismatched instances.
1066
+ } else if (!inRootOrSingleton || !enableHostSingletons) {
1067
+ // Match
1068
+ if (
1069
+ enableFormActions &&
1070
+ type === 'input' &&
1071
+ (element: any).type === 'hidden' &&
1072
+ anyProps.type !== 'hidden'
1073
+ ) {
1074
+ // Skip past hidden inputs unless that's what we're looking for. This allows us
1075
+ // embed extra form data in the original form.
1076
+ } else {
1077
+ return element;
1078
}
1115
- case 'script': {
1116
- // Scripts are a little tricky, we exclude known resources and then similar to links try to use high-entropy attributes
1117
- // to reject poor matches. One challenge with scripts are inline scripts. We don't attempt to check text content which could
1118
- // in theory lead to a hydration error later if a 3rd party injected an inline script before the React rendered nodes.
1119
- // Falling back to client rendering if this happens should be seemless though so we will try this hueristic and revisit later
1120
- // if we learn it is problematic
1121
- const srcAttr = element.getAttribute('src');
1122
- if (
1123
- srcAttr &&
1124
- element.hasAttribute('async') &&
1125
- !element.hasAttribute('itemprop')
1126
- ) {
1127
- // This is an async script resource
1128
- return true;
1129
- } else if (
1130
- srcAttr !== (anyProps.src == null ? null : anyProps.src) ||
1131
- element.getAttribute('type') !==
1132
- (anyProps.type == null ? null : anyProps.type) ||
1133
- element.getAttribute('crossorigin') !==
1134
- (anyProps.crossOrigin == null ? null : anyProps.crossOrigin)
1135
- ) {
1136
- // This script is for a different src
1137
- return true;
1079
+ } else if (isMarkedHoistable(element)) {
1080
+ // We've already claimed this as a hoistable which isn't hydrated this way so we skip past it.
1081
+ } else {
1082
+ // We have an Element with the right type.
1083
+
1084
+ // We are going to try to exclude it if we can definitely identify it as a hoisted Node or if
1085
+ // we can guess that the node is likely hoisted or was inserted by a 3rd party script or browser extension
1086
+ // using high entropy attributes for certain types. This technique will fail for strange insertions like
1087
+ // extension prepending <div> in the <body> but that already breaks before and that is an edge case.
1088
+ switch (type) {
1089
+ // case 'title':
1090
+ //We assume all titles are matchable. You should only have one in the Document, at least in a hoistable scope
1091
+ // and if you are a HostComponent with type title we must either be in an <svg> context or this title must have an `itemProp` prop.
1092
+ case 'meta': {
1093
+ // The only way to opt out of hoisting meta tags is to give it an itemprop attribute. We assume there will be
1094
+ // not 3rd party meta tags that are prepended, accepting the cases where this isn't true because meta tags
1095
+ // are usually only functional for SSR so even in a rare case where we did bind to an injected tag the runtime
1096
+ // implications are minimal
1097
+ if (!element.hasAttribute('itemprop')) {
1098
+ // This is a Hoistable
1099
+ break;
1100
+ }
1101
+ return element;
1102
+ }
1103
+ case 'link': {
1104
+ // Links come in many forms and we do expect 3rd parties to inject them into <head> / <body>. We exclude known resources
1105
+ // and then use high-entroy attributes like href which are almost always used and almost always unique to filter out unlikely
1106
+ // matches.
1107
+ const rel = element.getAttribute('rel');
1108
+ if (rel === 'stylesheet' && element.hasAttribute('data-precedence')) {
1109
+ // This is a stylesheet resource
1110
+ break;
1111
+ } else if (
1112
+ rel !== anyProps.rel ||
1113
+ element.getAttribute('href') !==
1114
+ (anyProps.href == null ? null : anyProps.href) ||
1115
+ element.getAttribute('crossorigin') !==
1116
+ (anyProps.crossOrigin == null ? null : anyProps.crossOrigin) ||
1117
+ element.getAttribute('title') !==
1118
+ (anyProps.title == null ? null : anyProps.title)
1119
+ ) {
1120
+ // rel + href should usually be enough to uniquely identify a link however crossOrigin can vary for rel preconnect
1121
+ // and title could vary for rel alternate
1122
+ break;
1123
+ }
1124
+ return element;
1125
+ }
1126
+ case 'style': {
1127
+ // Styles are hard to match correctly. We can exclude known resources but otherwise we accept the fact that a non-hoisted style tags
1128
+ // in <head> or <body> are likely never going to be unmounted given their position in the document and the fact they likely hold global styles
1129
+ if (element.hasAttribute('data-precedence')) {
1130
+ // This is a style resource
1131
+ break;
1132
+ }
1133
+ return element;
1134
+ }
1135
+ case 'script': {
1136
+ // Scripts are a little tricky, we exclude known resources and then similar to links try to use high-entropy attributes
1137
+ // to reject poor matches. One challenge with scripts are inline scripts. We don't attempt to check text content which could
1138
+ // in theory lead to a hydration error later if a 3rd party injected an inline script before the React rendered nodes.
1139
+ // Falling back to client rendering if this happens should be seemless though so we will try this hueristic and revisit later
1140
+ // if we learn it is problematic
1141
+ const srcAttr = element.getAttribute('src');
1142
+ if (
1143
+ srcAttr &&
1144
+ element.hasAttribute('async') &&
1145
+ !element.hasAttribute('itemprop')
1146
+ ) {
1147
+ // This is an async script resource
1148
+ break;
1149
+ } else if (
1150
+ srcAttr !== (anyProps.src == null ? null : anyProps.src) ||
1151
+ element.getAttribute('type') !==
1152
+ (anyProps.type == null ? null : anyProps.type) ||
1153
+ element.getAttribute('crossorigin') !==
1154
+ (anyProps.crossOrigin == null ? null : anyProps.crossOrigin)
1155
+ ) {
1156
+ // This script is for a different src
1157
+ break;
1158
+ }
1159
+ return element;
1160
+ }
1161
+ default: {
1162
+ // We have excluded the most likely cases of mismatch between hoistable tags, 3rd party script inserted tags,
1163
+ // and browser extension inserted tags. While it is possible this is not the right match it is a decent hueristic
1164
+ // that should work in the vast majority of cases.
1165
+ return element;
1166
}
1139
- break;
1167
}
1168
}
1142
- // We have excluded the most likely cases of mismatch between hoistable tags, 3rd party script inserted tags,
1143
- // and browser extension inserted tags. While it is possible this is not the right match it is a decent hueristic
1144
- // that should work in the vast majority of cases.
1145
- return false;
1146
- }
1147
-}
1148
-
1149
-export function shouldSkipHydratableForTextInstance(
1150
- instance: HydratableInstance,
1151
-): boolean {
1152
- return instance.nodeType === ELEMENT_NODE;
1153
-}
1154
-
1155
-export function shouldSkipHydratableForSuspenseInstance(
1156
- instance: HydratableInstance,
1157
-): boolean {
1158
- return instance.nodeType === ELEMENT_NODE;
1159
-}
1160
-
1161
-export function canHydrateInstance(
1162
- instance: HydratableInstance,
1163
- type: string,
1164
- props: Props,
1165
-): null | Instance {
1166
- if (
1167
- instance.nodeType !== ELEMENT_NODE ||
1168
- instance.nodeName.toLowerCase() !== type.toLowerCase()
1169
- ) {
1170
- return null;
1171
- } else {
1172
- return ((instance: any): Instance);
1169
+ const nextInstance = getNextHydratableSibling(element);
1170
+ if (nextInstance === null) {
1171
+ break;
1172
+ }
1173
+ instance = nextInstance;
1174
}
1175
+ // This is a suspense boundary or Text node or we got the end.
1176
+ // Suspense Boundaries are never expected to be injected by 3rd parties. If we see one it should be matched
1177
+ // and this is a hydration error.
1178
+ // Text Nodes are also not expected to be injected by 3rd parties. This is less of a guarantee for <body>
1179
+ // but it seems reasonable and conservative to reject this as a hydration error as well
1180
+ return null;
1181
}
1182
1183
export function canHydrateTextInstance(
1184
instance: HydratableInstance,
1185
text: string,
1186
+ inRootOrSingleton: boolean,
1187
): null | TextInstance {
1188
+ // Empty strings are not parsed by HTML so there won't be a correct match here.
1189
if (text === '') return null;
1190
1182
- if (instance.nodeType !== TEXT_NODE) {
1183
- // Empty strings are not parsed by HTML so there won't be a correct match here.
1184
- return null;
1191
+ while (instance.nodeType !== TEXT_NODE) {
1192
+ if (!inRootOrSingleton || !enableHostSingletons) {
1193
+ return null;
1194
+ }
1195
+ const nextInstance = getNextHydratableSibling(instance);
1196
+ if (nextInstance === null) {
1197
+ return null;
1198
+ }
1199
+ instance = nextInstance;
1200
}
1201
// This has now been refined to a text node.
1202
return ((instance: any): TextInstance);
@@ -1189,9 +1204,17 @@ export function canHydrateTextInstance(
1204
1205
export function canHydrateSuspenseInstance(
1206
instance: HydratableInstance,
1207
+ inRootOrSingleton: boolean,
1208
): null | SuspenseInstance {
1193
- if (instance.nodeType !== COMMENT_NODE) {
1194
- return null;
1209
+ while (instance.nodeType !== COMMENT_NODE) {
1210
+ if (!inRootOrSingleton || !enableHostSingletons) {
1211
+ return null;
1212
+ }
1213
+ const nextInstance = getNextHydratableSibling(instance);
1214
+ if (nextInstance === null) {
1215
+ return null;
1216
+ }
1217
+ instance = nextInstance;
1218
}
1219
// This has now been refined to a suspense node.
1220
return ((instance: any): SuspenseInstance);
@@ -1416,12 +1439,14 @@ export function commitHydratedSuspenseInstance(
1439
retryIfBlockedOn(suspenseInstance);
1440
}
1441
1419
-// @TODO remove this function once float lands and hydrated tail nodes
1420
-// are controlled by HostSingleton fibers
1442
export function shouldDeleteUnhydratedTailInstances(
1443
parentType: string,
1444
): boolean {
1424
- return parentType !== 'head' && parentType !== 'body';
1445
+ return (
1446
+ (enableHostSingletons ||
1447
+ (parentType !== 'head' && parentType !== 'body')) &&
1448
+ (!enableFormActions || parentType !== 'form')
1449
+ );
1450
}
1451
1452
export function didNotMatchHydratedContainerTextInstance(
packages/react-dom/src/__tests__/ReactServerRenderingHydration-test.js
+37
@@ -695,4 +695,41 @@ describe('ReactDOMServerHydration', () => {
695
);
696
}
697
});
698
+
699
+ // @gate enableFormActions
700
+ it('allows rendering extra hidden inputs in a form', async () => {
701
+ const element = document.createElement('div');
702
+ element.innerHTML =
703
+ '<form>' +
704
+ '<input type="hidden" /><input type="hidden" name="a" value="A" />' +
705
+ '<input type="hidden" /><input type="submit" name="b" value="B" />' +
706
+ '<input type="hidden" /><button name="c" value="C"></button>' +
707
+ '<input type="hidden" />' +
708
+ '</form>';
709
+ const form = element.firstChild;
710
+ const ref = React.createRef();
711
+ const a = React.createRef();
712
+ const b = React.createRef();
713
+ const c = React.createRef();
714
+ await act(async () => {
715
+ ReactDOMClient.hydrateRoot(
716
+ element,
717
+ <form ref={ref}>
718
+ <input type="hidden" name="a" value="A" ref={a} />
719
+ <input type="submit" name="b" value="B" ref={b} />
720
+ <button name="c" value="C" ref={c} />
721
+ </form>,
722
+ );
723
+ });
724
+
725
+ // The content should not have been client rendered.
726
+ expect(ref.current).toBe(form);
727
+
728
+ expect(a.current.name).toBe('a');
729
+ expect(a.current.value).toBe('A');
730
+ expect(b.current.name).toBe('b');
731
+ expect(b.current.value).toBe('B');
732
+ expect(c.current.name).toBe('c');
733
+ expect(c.current.value).toBe('C');
734
+ });
735
});
packages/react-reconciler/src/ReactFiberConfigWithNoHydration.js
-3
@@ -31,9 +31,6 @@ export const getNextHydratableSibling = shim;
31
export const getFirstHydratableChild = shim;
32
export const getFirstHydratableChildWithinContainer = shim;
33
export const getFirstHydratableChildWithinSuspenseInstance = shim;
34
-export const shouldSkipHydratableForInstance = shim;
35
-export const shouldSkipHydratableForTextInstance = shim;
36
-export const shouldSkipHydratableForSuspenseInstance = shim;
34
export const canHydrateInstance = shim;
35
export const canHydrateTextInstance = shim;
36
export const canHydrateSuspenseInstance = shim;
packages/react-reconciler/src/ReactFiberHydrationContext.js
+12
-72
@@ -74,9 +74,6 @@ import {
74
didNotFindHydratableTextInstance,
75
didNotFindHydratableSuspenseInstance,
76
resolveSingletonInstance,
77
- shouldSkipHydratableForInstance,
78
- shouldSkipHydratableForTextInstance,
79
- shouldSkipHydratableForSuspenseInstance,
77
canHydrateInstance,
78
canHydrateTextInstance,
79
canHydrateSuspenseInstance,
@@ -355,6 +352,7 @@ function tryHydrateInstance(fiber: Fiber, nextInstance: any) {
352
nextInstance,
353
fiber.type,
354
fiber.pendingProps,
355
+ rootOrSingletonContext,
356
);
357
if (instance !== null) {
358
fiber.stateNode = (instance: Instance);
@@ -369,7 +367,11 @@ function tryHydrateInstance(fiber: Fiber, nextInstance: any) {
367
function tryHydrateText(fiber: Fiber, nextInstance: any) {
368
// fiber is a HostText Fiber
369
const text = fiber.pendingProps;
372
- const textInstance = canHydrateTextInstance(nextInstance, text);
370
+ const textInstance = canHydrateTextInstance(
371
+ nextInstance,
372
+ text,
373
+ rootOrSingletonContext,
374
+ );
375
if (textInstance !== null) {
376
fiber.stateNode = (textInstance: TextInstance);
377
hydrationParentFiber = fiber;
@@ -382,7 +384,10 @@ function tryHydrateText(fiber: Fiber, nextInstance: any) {
384
385
function tryHydrateSuspense(fiber: Fiber, nextInstance: any) {
386
// fiber is a SuspenseComponent Fiber
385
- const suspenseInstance = canHydrateSuspenseInstance(nextInstance);
387
+ const suspenseInstance = canHydrateSuspenseInstance(
388
+ nextInstance,
389
+ rootOrSingletonContext,
390
+ );
391
if (suspenseInstance !== null) {
392
const suspenseState: SuspenseState = {
393
dehydrated: suspenseInstance,
@@ -441,44 +446,6 @@ function claimHydratableSingleton(fiber: Fiber): void {
446
}
447
}
448
444
-function advanceToFirstAttemptableInstance(fiber: Fiber) {
445
- // fiber is HostComponent Fiber
446
- while (
447
- nextHydratableInstance &&
448
- shouldSkipHydratableForInstance(
449
- nextHydratableInstance,
450
- fiber.type,
451
- fiber.pendingProps,
452
- )
453
- ) {
454
- // Flow doesn't understand that inside this block nextHydratableInstance is not null
455
- const instance: HydratableInstance = (nextHydratableInstance: any);
456
- nextHydratableInstance = getNextHydratableSibling(instance);
457
- }
458
-}
459
-
460
-function advanceToFirstAttemptableTextInstance() {
461
- while (
462
- nextHydratableInstance &&
463
- shouldSkipHydratableForTextInstance(nextHydratableInstance)
464
- ) {
465
- // Flow doesn't understand that inside this block nextHydratableInstance is not null
466
- const instance: HydratableInstance = (nextHydratableInstance: any);
467
- nextHydratableInstance = getNextHydratableSibling(instance);
468
- }
469
-}
470
-
471
-function advanceToFirstAttemptableSuspenseInstance() {
472
- while (
473
- nextHydratableInstance &&
474
- shouldSkipHydratableForSuspenseInstance(nextHydratableInstance)
475
- ) {
476
- // Flow doesn't understand that inside this block nextHydratableInstance is not null
477
- const instance: HydratableInstance = (nextHydratableInstance: any);
478
- nextHydratableInstance = getNextHydratableSibling(instance);
479
- }
480
-}
481
-
449
function tryToClaimNextHydratableInstance(fiber: Fiber): void {
450
if (!isHydrating) {
451
return;
@@ -493,10 +460,6 @@ function tryToClaimNextHydratableInstance(fiber: Fiber): void {
460
}
461
}
462
const initialInstance = nextHydratableInstance;
496
- if (rootOrSingletonContext) {
497
- // We may need to skip past certain nodes in these contexts
498
- advanceToFirstAttemptableInstance(fiber);
499
- }
463
const nextInstance = nextHydratableInstance;
464
if (!nextInstance) {
465
if (shouldClientRenderOnMismatch(fiber)) {
@@ -521,10 +484,6 @@ function tryToClaimNextHydratableInstance(fiber: Fiber): void {
484
// might be flawed or unnecessary.
485
nextHydratableInstance = getNextHydratableSibling(nextInstance);
486
const prevHydrationParentFiber: Fiber = (hydrationParentFiber: any);
524
- if (rootOrSingletonContext) {
525
- // We may need to skip past certain nodes in these contexts
526
- advanceToFirstAttemptableInstance(fiber);
527
- }
487
if (
488
!nextHydratableInstance ||
489
!tryHydrateInstance(fiber, nextHydratableInstance)
@@ -552,12 +511,6 @@ function tryToClaimNextHydratableTextInstance(fiber: Fiber): void {
511
const isHydratable = isHydratableText(text);
512
513
const initialInstance = nextHydratableInstance;
555
- if (rootOrSingletonContext && isHydratable) {
556
- // We may need to skip past certain nodes in these contexts.
557
- // We don't skip if the text is not hydratable because we know no hydratables
558
- // exist which could match this Fiber
559
- advanceToFirstAttemptableTextInstance();
560
- }
514
const nextInstance = nextHydratableInstance;
515
if (!nextInstance || !isHydratable) {
516
// We exclude non hydrabable text because we know there are no matching hydratables.
@@ -585,11 +538,6 @@ function tryToClaimNextHydratableTextInstance(fiber: Fiber): void {
538
nextHydratableInstance = getNextHydratableSibling(nextInstance);
539
const prevHydrationParentFiber: Fiber = (hydrationParentFiber: any);
540
588
- if (rootOrSingletonContext && isHydratable) {
589
- // We may need to skip past certain nodes in these contexts
590
- advanceToFirstAttemptableTextInstance();
591
- }
592
-
541
if (
542
!nextHydratableInstance ||
543
!tryHydrateText(fiber, nextHydratableInstance)
@@ -614,10 +562,6 @@ function tryToClaimNextHydratableSuspenseInstance(fiber: Fiber): void {
562
return;
563
}
564
const initialInstance = nextHydratableInstance;
617
- if (rootOrSingletonContext) {
618
- // We may need to skip past certain nodes in these contexts
619
- advanceToFirstAttemptableSuspenseInstance();
620
- }
565
const nextInstance = nextHydratableInstance;
566
if (!nextInstance) {
567
if (shouldClientRenderOnMismatch(fiber)) {
@@ -643,11 +587,6 @@ function tryToClaimNextHydratableSuspenseInstance(fiber: Fiber): void {
587
nextHydratableInstance = getNextHydratableSibling(nextInstance);
588
const prevHydrationParentFiber: Fiber = (hydrationParentFiber: any);
589
646
- if (rootOrSingletonContext) {
647
- // We may need to skip past certain nodes in these contexts
648
- advanceToFirstAttemptableSuspenseInstance();
649
- }
650
-
590
if (
591
!nextHydratableInstance ||
592
!tryHydrateSuspense(fiber, nextHydratableInstance)
@@ -863,7 +802,8 @@ function popHydrationState(fiber: Fiber): boolean {
802
fiber.tag !== HostSingleton &&
803
!(
804
fiber.tag === HostComponent &&
866
- shouldSetTextContent(fiber.type, fiber.memoizedProps)
805
+ (!shouldDeleteUnhydratedTailInstances(fiber.type) ||
806
+ shouldSetTextContent(fiber.type, fiber.memoizedProps))
807
)
808
) {
809
shouldClear = true;
packages/react-reconciler/src/forks/ReactFiberConfig.custom.js
-6
@@ -149,12 +149,6 @@ export const getFirstHydratableChildWithinContainer =
149
$$$config.getFirstHydratableChildWithinContainer;
150
export const getFirstHydratableChildWithinSuspenseInstance =
151
$$$config.getFirstHydratableChildWithinSuspenseInstance;
152
-export const shouldSkipHydratableForInstance =
153
- $$$config.shouldSkipHydratableForInstance;
154
-export const shouldSkipHydratableForTextInstance =
155
- $$$config.shouldSkipHydratableForTextInstance;
156
-export const shouldSkipHydratableForSuspenseInstance =
157
- $$$config.shouldSkipHydratableForSuspenseInstance;
152
export const canHydrateInstance = $$$config.canHydrateInstance;
153
export const canHydrateTextInstance = $$$config.canHydrateTextInstance;
154
export const canHydrateSuspenseInstance = $$$config.canHydrateSuspenseInstance;