Skip to content

Commit 1cec82b

Browse files
sebmarkbageAndyPengc12
authored andcommitted
Move Hydration Warnings from the DOM Config into the Fiber reconciliation (facebook#28476)
Stacked on facebook#28458. This doesn't actually really change the messages yet, it's just a refactor. Hydration warnings can be presented either as HTML or React JSX format. If presented as HTML it makes more sense to make that a DOM specific concept, however, I think it's actually better to present it in terms of React JSX. Most of the time the errors aren't going to be something messing with them at the HTML/HTTP layer. It's because the JS code does something different. Most of the time you're working in just React. People don't necessarily even know what the HTML form of it looks like. So this takes the approach that the warnings are presented in React JSX in their rich object form. Therefore, I'm moving the approach to yield diff data to the reconciler but it's the reconciler that's actually printing all the warnings.
1 parent 5b1748f commit 1cec82b

17 files changed

+606
-581
lines changed

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

Lines changed: 222 additions & 146 deletions
Large diffs are not rendered by default.

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

Lines changed: 52 additions & 187 deletions
Original file line numberDiff line numberDiff line change
@@ -52,14 +52,12 @@ import {hasRole} from './DOMAccessibilityRoles';
5252
import {
5353
setInitialProperties,
5454
updateProperties,
55+
hydrateProperties,
56+
hydrateText,
5557
diffHydratedProperties,
58+
getPropsFromElement,
5659
diffHydratedText,
5760
trapClickOnNonInteractiveElement,
58-
checkForUnmatchedText,
59-
warnForDeletedHydratableElement,
60-
warnForDeletedHydratableText,
61-
warnForInsertedHydratedElement,
62-
warnForInsertedHydratedText,
6361
} from './ReactDOMComponent';
6462
import {getSelectionInformation, restoreSelection} from './ReactInputSelection';
6563
import setTextContent from './setTextContent';
@@ -1342,6 +1340,26 @@ export function getFirstHydratableChildWithinSuspenseInstance(
13421340
return getNextHydratable(parentInstance.nextSibling);
13431341
}
13441342

1343+
export function describeHydratableInstanceForDevWarnings(
1344+
instance: HydratableInstance,
1345+
): string | {type: string, props: $ReadOnly<Props>} {
1346+
// Reverse engineer a pseudo react-element from hydratable instnace
1347+
if (instance.nodeType === ELEMENT_NODE) {
1348+
// Reverse engineer a set of props that can print for dev warnings
1349+
return {
1350+
type: instance.nodeName.toLowerCase(),
1351+
props: getPropsFromElement((instance: any)),
1352+
};
1353+
} else if (instance.nodeType === COMMENT_NODE) {
1354+
return {
1355+
type: 'Suspense',
1356+
props: {},
1357+
};
1358+
} else {
1359+
return instance.nodeValue;
1360+
}
1361+
}
1362+
13451363
export function validateHydratableInstance(
13461364
type: string,
13471365
props: Props,
@@ -1361,14 +1379,23 @@ export function hydrateInstance(
13611379
props: Props,
13621380
hostContext: HostContext,
13631381
internalInstanceHandle: Object,
1364-
shouldWarnDev: boolean,
1365-
): void {
1382+
): boolean {
13661383
precacheFiberNode(internalInstanceHandle, instance);
13671384
// TODO: Possibly defer this until the commit phase where all the events
13681385
// get attached.
13691386
updateFiberProps(instance, props);
13701387

1371-
diffHydratedProperties(instance, type, props, shouldWarnDev, hostContext);
1388+
return hydrateProperties(instance, type, props, hostContext);
1389+
}
1390+
1391+
// Returns a Map of properties that were different on the server.
1392+
export function diffHydratedPropsForDevWarnings(
1393+
instance: Instance,
1394+
type: string,
1395+
props: Props,
1396+
hostContext: HostContext,
1397+
): null | $ReadOnly<Props> {
1398+
return diffHydratedProperties(instance, type, props, hostContext);
13721399
}
13731400

13741401
export function validateHydratableTextInstance(
@@ -1389,11 +1416,26 @@ export function hydrateTextInstance(
13891416
textInstance: TextInstance,
13901417
text: string,
13911418
internalInstanceHandle: Object,
1392-
shouldWarnDev: boolean,
1419+
parentInstanceProps: null | Props,
13931420
): boolean {
13941421
precacheFiberNode(internalInstanceHandle, textInstance);
13951422

1396-
return diffHydratedText(textInstance, text);
1423+
return hydrateText(textInstance, text, parentInstanceProps);
1424+
}
1425+
1426+
// Returns the server text if it differs from the client.
1427+
export function diffHydratedTextForDevWarnings(
1428+
textInstance: TextInstance,
1429+
text: string,
1430+
parentProps: null | Props,
1431+
): null | string {
1432+
if (
1433+
parentProps === null ||
1434+
parentProps[SUPPRESS_HYDRATION_WARNING] !== true
1435+
) {
1436+
return diffHydratedText(textInstance, text);
1437+
}
1438+
return null;
13971439
}
13981440

13991441
export function hydrateSuspenseInstance(
@@ -1485,183 +1527,6 @@ export function shouldDeleteUnhydratedTailInstances(
14851527
return parentType !== 'form' && parentType !== 'button';
14861528
}
14871529

1488-
export function didNotMatchHydratedContainerTextInstance(
1489-
parentContainer: Container,
1490-
textInstance: TextInstance,
1491-
text: string,
1492-
shouldWarnDev: boolean,
1493-
) {
1494-
checkForUnmatchedText(textInstance.nodeValue, text, shouldWarnDev);
1495-
}
1496-
1497-
export function didNotMatchHydratedTextInstance(
1498-
parentType: string,
1499-
parentProps: Props,
1500-
parentInstance: Instance,
1501-
textInstance: TextInstance,
1502-
text: string,
1503-
shouldWarnDev: boolean,
1504-
) {
1505-
if (parentProps[SUPPRESS_HYDRATION_WARNING] !== true) {
1506-
checkForUnmatchedText(textInstance.nodeValue, text, shouldWarnDev);
1507-
}
1508-
}
1509-
1510-
export function didNotHydrateInstanceWithinContainer(
1511-
parentContainer: Container,
1512-
instance: HydratableInstance,
1513-
) {
1514-
if (__DEV__) {
1515-
if (instance.nodeType === ELEMENT_NODE) {
1516-
warnForDeletedHydratableElement(parentContainer, (instance: any));
1517-
} else if (instance.nodeType === COMMENT_NODE) {
1518-
// TODO: warnForDeletedHydratableSuspenseBoundary
1519-
} else {
1520-
warnForDeletedHydratableText(parentContainer, (instance: any));
1521-
}
1522-
}
1523-
}
1524-
1525-
export function didNotHydrateInstanceWithinSuspenseInstance(
1526-
parentInstance: SuspenseInstance,
1527-
instance: HydratableInstance,
1528-
) {
1529-
if (__DEV__) {
1530-
// $FlowFixMe[incompatible-type]: Only Element or Document can be parent nodes.
1531-
const parentNode: Element | Document | null = parentInstance.parentNode;
1532-
if (parentNode !== null) {
1533-
if (instance.nodeType === ELEMENT_NODE) {
1534-
warnForDeletedHydratableElement(parentNode, (instance: any));
1535-
} else if (instance.nodeType === COMMENT_NODE) {
1536-
// TODO: warnForDeletedHydratableSuspenseBoundary
1537-
} else {
1538-
warnForDeletedHydratableText(parentNode, (instance: any));
1539-
}
1540-
}
1541-
}
1542-
}
1543-
1544-
export function didNotHydrateInstance(
1545-
parentType: string,
1546-
parentProps: Props,
1547-
parentInstance: Instance,
1548-
instance: HydratableInstance,
1549-
) {
1550-
if (__DEV__) {
1551-
if (instance.nodeType === ELEMENT_NODE) {
1552-
warnForDeletedHydratableElement(parentInstance, (instance: any));
1553-
} else if (instance.nodeType === COMMENT_NODE) {
1554-
// TODO: warnForDeletedHydratableSuspenseBoundary
1555-
} else {
1556-
warnForDeletedHydratableText(parentInstance, (instance: any));
1557-
}
1558-
}
1559-
}
1560-
1561-
export function didNotFindHydratableInstanceWithinContainer(
1562-
parentContainer: Container,
1563-
type: string,
1564-
props: Props,
1565-
) {
1566-
if (__DEV__) {
1567-
warnForInsertedHydratedElement(parentContainer, type, props);
1568-
}
1569-
}
1570-
1571-
export function didNotFindHydratableTextInstanceWithinContainer(
1572-
parentContainer: Container,
1573-
text: string,
1574-
) {
1575-
if (__DEV__) {
1576-
warnForInsertedHydratedText(parentContainer, text);
1577-
}
1578-
}
1579-
1580-
export function didNotFindHydratableSuspenseInstanceWithinContainer(
1581-
parentContainer: Container,
1582-
) {
1583-
if (__DEV__) {
1584-
// TODO: warnForInsertedHydratedSuspense(parentContainer);
1585-
}
1586-
}
1587-
1588-
export function didNotFindHydratableInstanceWithinSuspenseInstance(
1589-
parentInstance: SuspenseInstance,
1590-
type: string,
1591-
props: Props,
1592-
) {
1593-
if (__DEV__) {
1594-
// $FlowFixMe[incompatible-type]: Only Element or Document can be parent nodes.
1595-
const parentNode: Element | Document | null = parentInstance.parentNode;
1596-
if (parentNode !== null)
1597-
warnForInsertedHydratedElement(parentNode, type, props);
1598-
}
1599-
}
1600-
1601-
export function didNotFindHydratableTextInstanceWithinSuspenseInstance(
1602-
parentInstance: SuspenseInstance,
1603-
text: string,
1604-
) {
1605-
if (__DEV__) {
1606-
// $FlowFixMe[incompatible-type]: Only Element or Document can be parent nodes.
1607-
const parentNode: Element | Document | null = parentInstance.parentNode;
1608-
if (parentNode !== null) warnForInsertedHydratedText(parentNode, text);
1609-
}
1610-
}
1611-
1612-
export function didNotFindHydratableSuspenseInstanceWithinSuspenseInstance(
1613-
parentInstance: SuspenseInstance,
1614-
) {
1615-
if (__DEV__) {
1616-
// const parentNode: Element | Document | null = parentInstance.parentNode;
1617-
// TODO: warnForInsertedHydratedSuspense(parentNode);
1618-
}
1619-
}
1620-
1621-
export function didNotFindHydratableInstance(
1622-
parentType: string,
1623-
parentProps: Props,
1624-
parentInstance: Instance,
1625-
type: string,
1626-
props: Props,
1627-
) {
1628-
if (__DEV__) {
1629-
warnForInsertedHydratedElement(parentInstance, type, props);
1630-
}
1631-
}
1632-
1633-
export function didNotFindHydratableTextInstance(
1634-
parentType: string,
1635-
parentProps: Props,
1636-
parentInstance: Instance,
1637-
text: string,
1638-
) {
1639-
if (__DEV__) {
1640-
warnForInsertedHydratedText(parentInstance, text);
1641-
}
1642-
}
1643-
1644-
export function didNotFindHydratableSuspenseInstance(
1645-
parentType: string,
1646-
parentProps: Props,
1647-
parentInstance: Instance,
1648-
) {
1649-
if (__DEV__) {
1650-
// TODO: warnForInsertedHydratedSuspense(parentInstance);
1651-
}
1652-
}
1653-
1654-
export function errorHydratingContainer(parentContainer: Container): void {
1655-
if (__DEV__) {
1656-
// TODO: This gets logged by onRecoverableError, too, so we should be
1657-
// able to remove it.
1658-
console.error(
1659-
'An error occurred during hydration. The server HTML was replaced with client content in <%s>.',
1660-
parentContainer.nodeName.toLowerCase(),
1661-
);
1662-
}
1663-
}
1664-
16651530
// -------------------
16661531
// Test Selectors
16671532
// -------------------

packages/react-dom/src/__tests__/ReactDOMFizzServer-test.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2407,8 +2407,8 @@ describe('ReactDOMFizzServer', () => {
24072407
]);
24082408
}).toErrorDev(
24092409
[
2410-
'Warning: An error occurred during hydration. The server HTML was replaced with client content in <div>.',
2411-
'Warning: Expected server HTML to contain a matching <div> in <div>.\n' +
2410+
'Warning: An error occurred during hydration. The server HTML was replaced with client content.',
2411+
'Warning: Expected server HTML to contain a matching <div> in the root.\n' +
24122412
' in div (at **)\n' +
24132413
' in App (at **)',
24142414
],
@@ -2492,7 +2492,7 @@ describe('ReactDOMFizzServer', () => {
24922492
}).toErrorDev(
24932493
[
24942494
'Warning: An error occurred during hydration. The server HTML was replaced with client content',
2495-
'Warning: Expected server HTML to contain a matching <div> in <div>.\n' +
2495+
'Warning: Expected server HTML to contain a matching <div> in the root.\n' +
24962496
' in div (at **)\n' +
24972497
' in App (at **)',
24982498
],
@@ -6343,7 +6343,7 @@ describe('ReactDOMFizzServer', () => {
63436343
await waitForAll([]);
63446344
}).toErrorDev(
63456345
[
6346-
'Expected server HTML to contain a matching <span> in <div>',
6346+
'Expected server HTML to contain a matching <span> in the root',
63476347
'An error occurred during hydration',
63486348
],
63496349
{withoutStack: 1},

packages/react-dom/src/__tests__/ReactDOMFizzSuppressHydrationWarning-test.js

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -248,7 +248,7 @@ describe('ReactDOMFizzServerHydrationWarning', () => {
248248
}).toErrorDev(
249249
[
250250
'Expected server HTML to contain a matching <span> in <span>',
251-
'An error occurred during hydration. The server HTML was replaced with client content in <div>.',
251+
'An error occurred during hydration. The server HTML was replaced with client content.',
252252
],
253253
{withoutStack: 1},
254254
);
@@ -337,7 +337,7 @@ describe('ReactDOMFizzServerHydrationWarning', () => {
337337
}).toErrorDev(
338338
[
339339
'Did not expect server HTML to contain the text node "Server" in <span>',
340-
'An error occurred during hydration. The server HTML was replaced with client content in <div>.',
340+
'An error occurred during hydration. The server HTML was replaced with client content.',
341341
],
342342
{withoutStack: 1},
343343
);
@@ -385,7 +385,7 @@ describe('ReactDOMFizzServerHydrationWarning', () => {
385385
}).toErrorDev(
386386
[
387387
'Expected server HTML to contain a matching text node for "Client" in <span>.',
388-
'An error occurred during hydration. The server HTML was replaced with client content in <div>.',
388+
'An error occurred during hydration. The server HTML was replaced with client content.',
389389
],
390390
{withoutStack: 1},
391391
);
@@ -436,7 +436,7 @@ describe('ReactDOMFizzServerHydrationWarning', () => {
436436
}).toErrorDev(
437437
[
438438
'Did not expect server HTML to contain the text node "Server" in <span>.',
439-
'An error occurred during hydration. The server HTML was replaced with client content in <div>.',
439+
'An error occurred during hydration. The server HTML was replaced with client content.',
440440
],
441441
{withoutStack: 1},
442442
);
@@ -485,7 +485,7 @@ describe('ReactDOMFizzServerHydrationWarning', () => {
485485
}).toErrorDev(
486486
[
487487
'Expected server HTML to contain a matching text node for "Client" in <span>.',
488-
'An error occurred during hydration. The server HTML was replaced with client content in <div>.',
488+
'An error occurred during hydration. The server HTML was replaced with client content.',
489489
],
490490
{withoutStack: 1},
491491
);
@@ -608,7 +608,7 @@ describe('ReactDOMFizzServerHydrationWarning', () => {
608608
}).toErrorDev(
609609
[
610610
'Expected server HTML to contain a matching <p> in <div>.',
611-
'An error occurred during hydration. The server HTML was replaced with client content in <div>.',
611+
'An error occurred during hydration. The server HTML was replaced with client content.',
612612
],
613613
{withoutStack: 1},
614614
);
@@ -654,7 +654,7 @@ describe('ReactDOMFizzServerHydrationWarning', () => {
654654
}).toErrorDev(
655655
[
656656
'Did not expect server HTML to contain a <p> in <div>.',
657-
'An error occurred during hydration. The server HTML was replaced with client content in <div>.',
657+
'An error occurred during hydration. The server HTML was replaced with client content.',
658658
],
659659
{withoutStack: 1},
660660
);

packages/react-dom/src/__tests__/ReactDOMFloat-test.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6481,7 +6481,7 @@ body {
64816481
}).toErrorDev(
64826482
[
64836483
'Warning: Text content did not match. Server: "server" Client: "client"',
6484-
'Warning: An error occurred during hydration. The server HTML was replaced with client content in <#document>.',
6484+
'Warning: An error occurred during hydration. The server HTML was replaced with client content.',
64856485
],
64866486
{withoutStack: 1},
64876487
);
@@ -8271,7 +8271,7 @@ background-color: green;
82718271
}).toErrorDev(
82728272
[
82738273
'Warning: Text content did not match. Server: "server" Client: "client"',
8274-
'Warning: An error occurred during hydration. The server HTML was replaced with client content in <#document>.',
8274+
'Warning: An error occurred during hydration. The server HTML was replaced with client content.',
82758275
],
82768276
{withoutStack: 1},
82778277
);

0 commit comments

Comments
 (0)