Skip to content

Commit 144bc71

Browse files
committed
Don't "fix up" mismatched text content with suppressedHydrationWarning
1 parent d310d65 commit 144bc71

File tree

3 files changed

+203
-196
lines changed

3 files changed

+203
-196
lines changed

packages/react-dom-bindings/src/client/ReactDOMComponent.js

Lines changed: 172 additions & 181 deletions
Original file line numberDiff line numberDiff line change
@@ -883,11 +883,9 @@ export function diffHydratedProperties(
883883
shouldWarnDev: boolean,
884884
parentNamespaceDev: string,
885885
): null | Array<mixed> {
886-
let isCustomComponentTag;
887886
let extraAttributeNames: Set<string>;
888887

889888
if (__DEV__) {
890-
isCustomComponentTag = isCustomComponent(tag, rawProps);
891889
validatePropertiesInDevelopment(tag, rawProps);
892890
}
893891

@@ -953,6 +951,10 @@ export function diffHydratedProperties(
953951
break;
954952
}
955953

954+
if (rawProps.hasOwnProperty('onScroll')) {
955+
listenToNonDelegatedEvent('scroll', domElement);
956+
}
957+
956958
assertValidProps(tag, rawProps);
957959

958960
if (__DEV__) {
@@ -978,207 +980,196 @@ export function diffHydratedProperties(
978980
}
979981

980982
let updatePayload = null;
981-
for (const propKey in rawProps) {
982-
if (!rawProps.hasOwnProperty(propKey)) {
983-
continue;
984-
}
985-
const nextProp = rawProps[propKey];
986-
if (propKey === CHILDREN) {
987-
// For text content children we compare against textContent. This
988-
// might match additional HTML that is hidden when we read it using
989-
// textContent. E.g. "foo" will match "f<span>oo</span>" but that still
990-
// satisfies our requirement. Our requirement is not to produce perfect
991-
// HTML and attributes. Ideally we should preserve structure but it's
992-
// ok not to if the visible content is still enough to indicate what
993-
// even listeners these nodes might be wired up to.
994-
// TODO: Warn if there is more than a single textNode as a child.
995-
// TODO: Should we use domElement.firstChild.nodeValue to compare?
996-
if (typeof nextProp === 'string') {
997-
if (domElement.textContent !== nextProp) {
998-
if (rawProps[SUPPRESS_HYDRATION_WARNING] !== true) {
999-
checkForUnmatchedText(
1000-
domElement.textContent,
1001-
nextProp,
1002-
isConcurrentMode,
1003-
shouldWarnDev,
1004-
);
1005-
}
1006-
updatePayload = [CHILDREN, nextProp];
1007-
}
1008-
} else if (typeof nextProp === 'number') {
1009-
if (domElement.textContent !== '' + nextProp) {
1010-
if (rawProps[SUPPRESS_HYDRATION_WARNING] !== true) {
1011-
checkForUnmatchedText(
1012-
domElement.textContent,
1013-
nextProp,
1014-
isConcurrentMode,
1015-
shouldWarnDev,
1016-
);
1017-
}
1018-
updatePayload = [CHILDREN, '' + nextProp];
1019-
}
983+
984+
const children = rawProps.children;
985+
// For text content children we compare against textContent. This
986+
// might match additional HTML that is hidden when we read it using
987+
// textContent. E.g. "foo" will match "f<span>oo</span>" but that still
988+
// satisfies our requirement. Our requirement is not to produce perfect
989+
// HTML and attributes. Ideally we should preserve structure but it's
990+
// ok not to if the visible content is still enough to indicate what
991+
// even listeners these nodes might be wired up to.
992+
// TODO: Warn if there is more than a single textNode as a child.
993+
// TODO: Should we use domElement.firstChild.nodeValue to compare?
994+
if (typeof children === 'string' || typeof children === 'number') {
995+
if (domElement.textContent !== '' + children) {
996+
if (rawProps[SUPPRESS_HYDRATION_WARNING] !== true) {
997+
checkForUnmatchedText(
998+
domElement.textContent,
999+
children,
1000+
isConcurrentMode,
1001+
shouldWarnDev,
1002+
);
10201003
}
1021-
} else if (registrationNameDependencies.hasOwnProperty(propKey)) {
1022-
if (nextProp != null) {
1023-
if (__DEV__ && typeof nextProp !== 'function') {
1024-
warnForInvalidEventListener(propKey, nextProp);
1025-
}
1026-
if (propKey === 'onScroll') {
1027-
listenToNonDelegatedEvent('scroll', domElement);
1028-
}
1004+
if (!isConcurrentMode) {
1005+
updatePayload = [CHILDREN, children];
10291006
}
1030-
} else if (
1031-
shouldWarnDev &&
1032-
__DEV__ &&
1033-
// Convince Flow we've calculated it (it's DEV-only in this method.)
1034-
typeof isCustomComponentTag === 'boolean'
1035-
) {
1036-
// Validate that the properties correspond to their expected values.
1037-
let serverValue;
1038-
const propertyInfo =
1039-
isCustomComponentTag && enableCustomElementPropertySupport
1040-
? null
1041-
: getPropertyInfo(propKey);
1042-
if (rawProps[SUPPRESS_HYDRATION_WARNING] === true) {
1043-
// Don't bother comparing. We're ignoring all these warnings.
1044-
} else if (
1045-
propKey === SUPPRESS_CONTENT_EDITABLE_WARNING ||
1046-
propKey === SUPPRESS_HYDRATION_WARNING ||
1047-
// Controlled attributes are not validated
1048-
// TODO: Only ignore them on controlled tags.
1049-
propKey === 'value' ||
1050-
propKey === 'checked' ||
1051-
propKey === 'selected'
1052-
) {
1053-
// Noop
1054-
} else if (propKey === DANGEROUSLY_SET_INNER_HTML) {
1055-
const serverHTML = domElement.innerHTML;
1056-
const nextHtml = nextProp ? nextProp[HTML] : undefined;
1057-
if (nextHtml != null) {
1058-
const expectedHTML = normalizeHTML(domElement, nextHtml);
1059-
if (expectedHTML !== serverHTML) {
1060-
warnForPropDifference(propKey, serverHTML, expectedHTML);
1007+
}
1008+
}
1009+
1010+
if (__DEV__ && shouldWarnDev) {
1011+
const isCustomComponentTag = isCustomComponent(tag, rawProps);
1012+
1013+
for (const propKey in rawProps) {
1014+
if (!rawProps.hasOwnProperty(propKey)) {
1015+
continue;
1016+
}
1017+
const nextProp = rawProps[propKey];
1018+
if (propKey === CHILDREN) {
1019+
// Checked above already
1020+
} else if (registrationNameDependencies.hasOwnProperty(propKey)) {
1021+
if (nextProp != null) {
1022+
if (typeof nextProp !== 'function') {
1023+
warnForInvalidEventListener(propKey, nextProp);
10611024
}
10621025
}
1063-
} else if (propKey === STYLE) {
1064-
// $FlowFixMe - Should be inferred as not undefined.
1065-
extraAttributeNames.delete(propKey);
1066-
1067-
if (canDiffStyleForHydrationWarning) {
1068-
const expectedStyle = createDangerousStringForStyles(nextProp);
1069-
serverValue = domElement.getAttribute('style');
1070-
if (expectedStyle !== serverValue) {
1071-
warnForPropDifference(propKey, serverValue, expectedStyle);
1026+
} else {
1027+
// Validate that the properties correspond to their expected values.
1028+
let serverValue;
1029+
const propertyInfo =
1030+
isCustomComponentTag && enableCustomElementPropertySupport
1031+
? null
1032+
: getPropertyInfo(propKey);
1033+
if (rawProps[SUPPRESS_HYDRATION_WARNING] === true) {
1034+
// Don't bother comparing. We're ignoring all these warnings.
1035+
} else if (
1036+
propKey === SUPPRESS_CONTENT_EDITABLE_WARNING ||
1037+
propKey === SUPPRESS_HYDRATION_WARNING ||
1038+
// Controlled attributes are not validated
1039+
// TODO: Only ignore them on controlled tags.
1040+
propKey === 'value' ||
1041+
propKey === 'checked' ||
1042+
propKey === 'selected'
1043+
) {
1044+
// Noop
1045+
} else if (propKey === DANGEROUSLY_SET_INNER_HTML) {
1046+
const serverHTML = domElement.innerHTML;
1047+
const nextHtml = nextProp ? nextProp[HTML] : undefined;
1048+
if (nextHtml != null) {
1049+
const expectedHTML = normalizeHTML(domElement, nextHtml);
1050+
if (expectedHTML !== serverHTML) {
1051+
warnForPropDifference(propKey, serverHTML, expectedHTML);
1052+
}
10721053
}
1073-
}
1074-
} else if (
1075-
enableCustomElementPropertySupport &&
1076-
isCustomComponentTag &&
1077-
(propKey === 'offsetParent' ||
1078-
propKey === 'offsetTop' ||
1079-
propKey === 'offsetLeft' ||
1080-
propKey === 'offsetWidth' ||
1081-
propKey === 'offsetHeight' ||
1082-
propKey === 'isContentEditable' ||
1083-
propKey === 'outerText' ||
1084-
propKey === 'outerHTML')
1085-
) {
1086-
// $FlowFixMe - Should be inferred as not undefined.
1087-
extraAttributeNames.delete(propKey.toLowerCase());
1088-
if (__DEV__) {
1089-
console.error(
1090-
'Assignment to read-only property will result in a no-op: `%s`',
1091-
propKey,
1092-
);
1093-
}
1094-
} else if (isCustomComponentTag && !enableCustomElementPropertySupport) {
1095-
// $FlowFixMe - Should be inferred as not undefined.
1096-
extraAttributeNames.delete(propKey.toLowerCase());
1097-
serverValue = getValueForAttribute(
1098-
domElement,
1099-
propKey,
1100-
nextProp,
1101-
isCustomComponentTag,
1102-
);
1054+
} else if (propKey === STYLE) {
1055+
// $FlowFixMe - Should be inferred as not undefined.
1056+
extraAttributeNames.delete(propKey);
11031057

1104-
if (nextProp !== serverValue) {
1105-
warnForPropDifference(propKey, serverValue, nextProp);
1106-
}
1107-
} else if (
1108-
!shouldIgnoreAttribute(propKey, propertyInfo, isCustomComponentTag) &&
1109-
!shouldRemoveAttribute(
1110-
propKey,
1111-
nextProp,
1112-
propertyInfo,
1113-
isCustomComponentTag,
1114-
)
1115-
) {
1116-
let isMismatchDueToBadCasing = false;
1117-
if (propertyInfo !== null) {
1058+
if (canDiffStyleForHydrationWarning) {
1059+
const expectedStyle = createDangerousStringForStyles(nextProp);
1060+
serverValue = domElement.getAttribute('style');
1061+
if (expectedStyle !== serverValue) {
1062+
warnForPropDifference(propKey, serverValue, expectedStyle);
1063+
}
1064+
}
1065+
} else if (
1066+
enableCustomElementPropertySupport &&
1067+
isCustomComponentTag &&
1068+
(propKey === 'offsetParent' ||
1069+
propKey === 'offsetTop' ||
1070+
propKey === 'offsetLeft' ||
1071+
propKey === 'offsetWidth' ||
1072+
propKey === 'offsetHeight' ||
1073+
propKey === 'isContentEditable' ||
1074+
propKey === 'outerText' ||
1075+
propKey === 'outerHTML')
1076+
) {
1077+
// $FlowFixMe - Should be inferred as not undefined.
1078+
extraAttributeNames.delete(propKey.toLowerCase());
1079+
if (__DEV__) {
1080+
console.error(
1081+
'Assignment to read-only property will result in a no-op: `%s`',
1082+
propKey,
1083+
);
1084+
}
1085+
} else if (
1086+
isCustomComponentTag &&
1087+
!enableCustomElementPropertySupport
1088+
) {
11181089
// $FlowFixMe - Should be inferred as not undefined.
1119-
extraAttributeNames.delete(propertyInfo.attributeName);
1120-
serverValue = getValueForProperty(
1090+
extraAttributeNames.delete(propKey.toLowerCase());
1091+
serverValue = getValueForAttribute(
11211092
domElement,
11221093
propKey,
11231094
nextProp,
1124-
propertyInfo,
1095+
isCustomComponentTag,
11251096
);
1126-
} else {
1127-
let ownNamespaceDev = parentNamespaceDev;
1128-
if (ownNamespaceDev === HTML_NAMESPACE) {
1129-
ownNamespaceDev = getIntrinsicNamespace(tag);
1097+
1098+
if (nextProp !== serverValue) {
1099+
warnForPropDifference(propKey, serverValue, nextProp);
11301100
}
1131-
if (ownNamespaceDev === HTML_NAMESPACE) {
1101+
} else if (
1102+
!shouldIgnoreAttribute(propKey, propertyInfo, isCustomComponentTag) &&
1103+
!shouldRemoveAttribute(
1104+
propKey,
1105+
nextProp,
1106+
propertyInfo,
1107+
isCustomComponentTag,
1108+
)
1109+
) {
1110+
let isMismatchDueToBadCasing = false;
1111+
if (propertyInfo !== null) {
11321112
// $FlowFixMe - Should be inferred as not undefined.
1133-
extraAttributeNames.delete(propKey.toLowerCase());
1113+
extraAttributeNames.delete(propertyInfo.attributeName);
1114+
serverValue = getValueForProperty(
1115+
domElement,
1116+
propKey,
1117+
nextProp,
1118+
propertyInfo,
1119+
);
11341120
} else {
1135-
const standardName = getPossibleStandardName(propKey);
1136-
if (standardName !== null && standardName !== propKey) {
1137-
// If an SVG prop is supplied with bad casing, it will
1138-
// be successfully parsed from HTML, but will produce a mismatch
1139-
// (and would be incorrectly rendered on the client).
1140-
// However, we already warn about bad casing elsewhere.
1141-
// So we'll skip the misleading extra mismatch warning in this case.
1142-
isMismatchDueToBadCasing = true;
1121+
let ownNamespaceDev = parentNamespaceDev;
1122+
if (ownNamespaceDev === HTML_NAMESPACE) {
1123+
ownNamespaceDev = getIntrinsicNamespace(tag);
1124+
}
1125+
if (ownNamespaceDev === HTML_NAMESPACE) {
1126+
// $FlowFixMe - Should be inferred as not undefined.
1127+
extraAttributeNames.delete(propKey.toLowerCase());
1128+
} else {
1129+
const standardName = getPossibleStandardName(propKey);
1130+
if (standardName !== null && standardName !== propKey) {
1131+
// If an SVG prop is supplied with bad casing, it will
1132+
// be successfully parsed from HTML, but will produce a mismatch
1133+
// (and would be incorrectly rendered on the client).
1134+
// However, we already warn about bad casing elsewhere.
1135+
// So we'll skip the misleading extra mismatch warning in this case.
1136+
isMismatchDueToBadCasing = true;
1137+
// $FlowFixMe - Should be inferred as not undefined.
1138+
extraAttributeNames.delete(standardName);
1139+
}
11431140
// $FlowFixMe - Should be inferred as not undefined.
1144-
extraAttributeNames.delete(standardName);
1141+
extraAttributeNames.delete(propKey);
11451142
}
1146-
// $FlowFixMe - Should be inferred as not undefined.
1147-
extraAttributeNames.delete(propKey);
1143+
serverValue = getValueForAttribute(
1144+
domElement,
1145+
propKey,
1146+
nextProp,
1147+
isCustomComponentTag,
1148+
);
11481149
}
1149-
serverValue = getValueForAttribute(
1150-
domElement,
1151-
propKey,
1152-
nextProp,
1153-
isCustomComponentTag,
1154-
);
1155-
}
11561150

1157-
const dontWarnCustomElement =
1158-
enableCustomElementPropertySupport &&
1159-
isCustomComponentTag &&
1160-
(typeof nextProp === 'function' || typeof nextProp === 'object');
1161-
if (
1162-
!dontWarnCustomElement &&
1163-
nextProp !== serverValue &&
1164-
!isMismatchDueToBadCasing
1165-
) {
1166-
warnForPropDifference(propKey, serverValue, nextProp);
1151+
const dontWarnCustomElement =
1152+
enableCustomElementPropertySupport &&
1153+
isCustomComponentTag &&
1154+
(typeof nextProp === 'function' || typeof nextProp === 'object');
1155+
if (
1156+
!dontWarnCustomElement &&
1157+
nextProp !== serverValue &&
1158+
!isMismatchDueToBadCasing
1159+
) {
1160+
warnForPropDifference(propKey, serverValue, nextProp);
1161+
}
11671162
}
11681163
}
11691164
}
1170-
}
11711165

1172-
if (__DEV__) {
1173-
if (shouldWarnDev) {
1174-
if (
1175-
// $FlowFixMe - Should be inferred as not undefined.
1176-
extraAttributeNames.size > 0 &&
1177-
rawProps[SUPPRESS_HYDRATION_WARNING] !== true
1178-
) {
1179-
// $FlowFixMe - Should be inferred as not undefined.
1180-
warnForExtraAttributes(extraAttributeNames);
1181-
}
1166+
if (
1167+
// $FlowFixMe - Should be inferred as not undefined.
1168+
extraAttributeNames.size > 0 &&
1169+
rawProps[SUPPRESS_HYDRATION_WARNING] !== true
1170+
) {
1171+
// $FlowFixMe - Should be inferred as not undefined.
1172+
warnForExtraAttributes(extraAttributeNames);
11821173
}
11831174
}
11841175

0 commit comments

Comments
 (0)