Skip to content

Commit

Permalink
Proof-of-concept hydrate warning diff (facebook#10085)
Browse files Browse the repository at this point in the history
Example warning:
```
Warning: Expected server HTML to contain a matching <em> in <div>.
 <div className="SSRMismatchTest__wrapper">
   …
   <span className="SSRMismatchTest__2">2</span>
   <span className="SSRMismatchTest__3">3</span>
   <span className="SSRMismatchTest__4">4</span>
   <span className="SSRMismatchTest__5">5</span>
   <span className="SSRMismatchTest__6">6</span>
-  <strong> SSRMismatchTest default text </strong>
+  <em />
   <span className="SSRMismatchTest__7">7</span>
   <span className="SSRMismatchTest__8">8</span>
   <span className="SSRMismatchTest__9">9</span>
   <span className="SSRMismatchTest__10">10</span>
   <span className="SSRMismatchTest__11">11</span>
   …
 </div>

    in em (at SSRMismatchTest.js:224)
    in div (at SSRMismatchTest.js:217)
    in div (at SSRMismatchTest.js:283)
    in SSRMismatchTest (at App.js:14)
    in div (at App.js:11)
    in body (at Chrome.js:17)
    in html (at Chrome.js:9)
    in Chrome (at App.js:10)
    in App (at index.js:8)
```

https://user-images.githubusercontent.com/498274/36351251-d04e8fca-145b-11e8-995d-389e0ae99456.png
  • Loading branch information
sompylasar committed Feb 18, 2018
1 parent 295df4c commit 6c425e7
Show file tree
Hide file tree
Showing 6 changed files with 108 additions and 34 deletions.
32 changes: 28 additions & 4 deletions fixtures/ssr/src/components/SSRMismatchTest.js
Original file line number Diff line number Diff line change
Expand Up @@ -196,14 +196,38 @@ const testCases = [
{
key: 'ssr-warnForInsertedHydratedElement-didNotFindHydratableInstance',
renderServer: () => (
<div>
<em>SSRMismatchTest default text</em>
<div className="SSRMismatchTest__wrapper">
<span className="SSRMismatchTest__1">1</span>
<span className="SSRMismatchTest__2">2</span>
<span className="SSRMismatchTest__3">3</span>
<span className="SSRMismatchTest__4">4</span>
<span className="SSRMismatchTest__5">5</span>
<span className="SSRMismatchTest__6">6</span>
<strong> SSRMismatchTest default text </strong>
<span className="SSRMismatchTest__7">7</span>
<span className="SSRMismatchTest__8">8</span>
<span className="SSRMismatchTest__9">9</span>
<span className="SSRMismatchTest__10">10</span>
<span className="SSRMismatchTest__11">11</span>
<span className="SSRMismatchTest__12">12</span>
</div>
),
renderBrowser: () => (
// The inner element type is different from the server render, but the inner text is the same.
<div>
<p>SSRMismatchTest default text</p>
<div className="SSRMismatchTest__wrapper">
<span className="SSRMismatchTest__1">1</span>
<span className="SSRMismatchTest__2">2</span>
<span className="SSRMismatchTest__3">3</span>
<span className="SSRMismatchTest__4">4</span>
<span className="SSRMismatchTest__5">5</span>
<span className="SSRMismatchTest__6">6</span>
<em> SSRMismatchTest default text </em>
<span className="SSRMismatchTest__7">7</span>
<span className="SSRMismatchTest__8">8</span>
<span className="SSRMismatchTest__9">9</span>
<span className="SSRMismatchTest__10">10</span>
<span className="SSRMismatchTest__11">11</span>
<span className="SSRMismatchTest__12">12</span>
</div>
),
},
Expand Down
5 changes: 3 additions & 2 deletions packages/react-dom/src/client/ReactDOM.js
Original file line number Diff line number Diff line change
Expand Up @@ -948,7 +948,7 @@ const DOMRenderer = ReactFiberReconciler({
props: Props,
) {
if (__DEV__) {
warnForInsertedHydratedElement(parentContainer, type, props);
warnForInsertedHydratedElement(parentContainer, type, props, 0);
}
},

Expand All @@ -967,9 +967,10 @@ const DOMRenderer = ReactFiberReconciler({
parentInstance: Instance,
type: string,
props: Props,
index: number,
) {
if (__DEV__ && parentProps[SUPPRESS_HYDRATION_WARNING] !== true) {
warnForInsertedHydratedElement(parentInstance, type, props);
warnForInsertedHydratedElement(parentInstance, type, props, index);
}
},

Expand Down
51 changes: 49 additions & 2 deletions packages/react-dom/src/client/ReactDOMFiberComponent.js
Original file line number Diff line number Diff line change
Expand Up @@ -1146,17 +1146,64 @@ export function warnForInsertedHydratedElement(
parentNode: Element | Document,
tag: string,
props: Object,
index: number,
) {
if (__DEV__) {
if (didWarnInvalidHydration) {
return;
}
didWarnInvalidHydration = true;
let htmlContext = [];
const ic = parentNode.childNodes.length;
const parentNodeName = parentNode.nodeName.toLowerCase();
htmlContext.push(
' <' +
parentNodeName +
(parentNode.className
? ' className="' + parentNode.className + '"'
: '') +
'>',
);
if (index - 5 > 0) {
htmlContext.push(' …');
}
for (let i = index - 5; i <= index + 5; ++i) {
if (i >= 0 && i < ic) {
const childNode = parentNode.childNodes[i];
const childNodeName = childNode.nodeName.toLowerCase();
htmlContext.push(
(i === index ? '- ' : ' ') +
(childNode.nodeType === 1
? '<' +
childNodeName +
(childNode.className
? ' className="' + childNode.className + '"'
: '') +
(childNode.textContent
? '>' + childNode.textContent + '</' + childNodeName + '>'
: ' />')
: childNode.textContent),
);
if (i === index) {
htmlContext.push(
'+ <' +
tag +
(props.className ? ' className="' + props.className + '"' : '') +
' />',
);
}
}
}
if (index + 5 < ic - 1) {
htmlContext.push(' …');
}
htmlContext.push(' </' + parentNodeName + '>');
warning(
false,
'Expected server HTML to contain a matching <%s> in <%s>.%s',
'Expected server HTML to contain a matching <%s> in <%s>.%s%s',
tag,
parentNode.nodeName.toLowerCase(),
parentNodeName,
'\n' + htmlContext.join('\n') + '\n',
getStack(),
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,7 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
parentInstance,
type,
props,
fiber.index,
);
break;
case HostText:
Expand Down
1 change: 1 addition & 0 deletions packages/react-reconciler/src/ReactFiberReconciler.js
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,7 @@ type HydrationHostConfig<T, P, I, TI, HI, C, CX, PL> = {
parentInstance: I,
type: T,
props: P,
index: number,
): void,
didNotFindHydratableTextInstance(
parentType: T,
Expand Down
52 changes: 26 additions & 26 deletions scripts/rollup/results.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,22 +46,22 @@
"filename": "react-dom.development.js",
"bundleType": "UMD_DEV",
"packageName": "react-dom",
"size": 591567,
"gzip": 138758
"size": 592727,
"gzip": 139090
},
{
"filename": "react-dom.production.min.js",
"bundleType": "UMD_PROD",
"packageName": "react-dom",
"size": 96778,
"gzip": 31445
"size": 96782,
"gzip": 31444
},
{
"filename": "react-dom.development.js",
"bundleType": "NODE_DEV",
"packageName": "react-dom",
"size": 575580,
"gzip": 134533
"size": 576740,
"gzip": 134864
},
{
"filename": "react-dom.production.min.js",
Expand All @@ -74,8 +74,8 @@
"filename": "ReactDOM-dev.js",
"bundleType": "FB_DEV",
"packageName": "react-dom",
"size": 594873,
"gzip": 136788
"size": 596363,
"gzip": 137178
},
{
"filename": "ReactDOM-prod.js",
Expand Down Expand Up @@ -221,8 +221,8 @@
"filename": "react-art.development.js",
"bundleType": "UMD_DEV",
"packageName": "react-art",
"size": 389869,
"gzip": 86413
"size": 389882,
"gzip": 86423
},
{
"filename": "react-art.production.min.js",
Expand All @@ -235,8 +235,8 @@
"filename": "react-art.development.js",
"bundleType": "NODE_DEV",
"packageName": "react-art",
"size": 313942,
"gzip": 67385
"size": 313955,
"gzip": 67392
},
{
"filename": "react-art.production.min.js",
Expand All @@ -249,8 +249,8 @@
"filename": "ReactART-dev.js",
"bundleType": "FB_DEV",
"packageName": "react-art",
"size": 318024,
"gzip": 66603
"size": 318053,
"gzip": 66612
},
{
"filename": "ReactART-prod.js",
Expand All @@ -263,8 +263,8 @@
"filename": "ReactNativeRenderer-dev.js",
"bundleType": "RN_DEV",
"packageName": "react-native-renderer",
"size": 443941,
"gzip": 97414
"size": 443970,
"gzip": 97423
},
{
"filename": "ReactNativeRenderer-prod.js",
Expand All @@ -277,8 +277,8 @@
"filename": "react-test-renderer.development.js",
"bundleType": "NODE_DEV",
"packageName": "react-test-renderer",
"size": 311062,
"gzip": 66359
"size": 311075,
"gzip": 66366
},
{
"filename": "react-test-renderer.production.min.js",
Expand All @@ -291,8 +291,8 @@
"filename": "ReactTestRenderer-dev.js",
"bundleType": "FB_DEV",
"packageName": "react-test-renderer",
"size": 315158,
"gzip": 65549
"size": 315187,
"gzip": 65558
},
{
"filename": "react-test-renderer-shallow.development.js",
Expand Down Expand Up @@ -333,8 +333,8 @@
"filename": "react-reconciler.development.js",
"bundleType": "NODE_DEV",
"packageName": "react-reconciler",
"size": 292377,
"gzip": 61765
"size": 292390,
"gzip": 61771
},
{
"filename": "react-reconciler.production.min.js",
Expand Down Expand Up @@ -375,8 +375,8 @@
"filename": "ReactFabric-dev.js",
"bundleType": "RN_DEV",
"packageName": "react-native-renderer",
"size": 438218,
"gzip": 96267
"size": 438247,
"gzip": 96276
},
{
"filename": "ReactFabric-prod.js",
Expand All @@ -389,8 +389,8 @@
"filename": "react-reconciler-persistent.development.js",
"bundleType": "NODE_DEV",
"packageName": "react-reconciler",
"size": 291949,
"gzip": 61587
"size": 291962,
"gzip": 61594
},
{
"filename": "react-reconciler-persistent.production.min.js",
Expand Down

0 comments on commit 6c425e7

Please sign in to comment.