Skip to content

Commit 6c425e7

Browse files
committed
Proof-of-concept hydrate warning diff (facebook#10085)
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
1 parent 295df4c commit 6c425e7

File tree

6 files changed

+108
-34
lines changed

6 files changed

+108
-34
lines changed

fixtures/ssr/src/components/SSRMismatchTest.js

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -196,14 +196,38 @@ const testCases = [
196196
{
197197
key: 'ssr-warnForInsertedHydratedElement-didNotFindHydratableInstance',
198198
renderServer: () => (
199-
<div>
200-
<em>SSRMismatchTest default text</em>
199+
<div className="SSRMismatchTest__wrapper">
200+
<span className="SSRMismatchTest__1">1</span>
201+
<span className="SSRMismatchTest__2">2</span>
202+
<span className="SSRMismatchTest__3">3</span>
203+
<span className="SSRMismatchTest__4">4</span>
204+
<span className="SSRMismatchTest__5">5</span>
205+
<span className="SSRMismatchTest__6">6</span>
206+
<strong> SSRMismatchTest default text </strong>
207+
<span className="SSRMismatchTest__7">7</span>
208+
<span className="SSRMismatchTest__8">8</span>
209+
<span className="SSRMismatchTest__9">9</span>
210+
<span className="SSRMismatchTest__10">10</span>
211+
<span className="SSRMismatchTest__11">11</span>
212+
<span className="SSRMismatchTest__12">12</span>
201213
</div>
202214
),
203215
renderBrowser: () => (
204216
// The inner element type is different from the server render, but the inner text is the same.
205-
<div>
206-
<p>SSRMismatchTest default text</p>
217+
<div className="SSRMismatchTest__wrapper">
218+
<span className="SSRMismatchTest__1">1</span>
219+
<span className="SSRMismatchTest__2">2</span>
220+
<span className="SSRMismatchTest__3">3</span>
221+
<span className="SSRMismatchTest__4">4</span>
222+
<span className="SSRMismatchTest__5">5</span>
223+
<span className="SSRMismatchTest__6">6</span>
224+
<em> SSRMismatchTest default text </em>
225+
<span className="SSRMismatchTest__7">7</span>
226+
<span className="SSRMismatchTest__8">8</span>
227+
<span className="SSRMismatchTest__9">9</span>
228+
<span className="SSRMismatchTest__10">10</span>
229+
<span className="SSRMismatchTest__11">11</span>
230+
<span className="SSRMismatchTest__12">12</span>
207231
</div>
208232
),
209233
},

packages/react-dom/src/client/ReactDOM.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -948,7 +948,7 @@ const DOMRenderer = ReactFiberReconciler({
948948
props: Props,
949949
) {
950950
if (__DEV__) {
951-
warnForInsertedHydratedElement(parentContainer, type, props);
951+
warnForInsertedHydratedElement(parentContainer, type, props, 0);
952952
}
953953
},
954954

@@ -967,9 +967,10 @@ const DOMRenderer = ReactFiberReconciler({
967967
parentInstance: Instance,
968968
type: string,
969969
props: Props,
970+
index: number,
970971
) {
971972
if (__DEV__ && parentProps[SUPPRESS_HYDRATION_WARNING] !== true) {
972-
warnForInsertedHydratedElement(parentInstance, type, props);
973+
warnForInsertedHydratedElement(parentInstance, type, props, index);
973974
}
974975
},
975976

packages/react-dom/src/client/ReactDOMFiberComponent.js

Lines changed: 49 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1146,17 +1146,64 @@ export function warnForInsertedHydratedElement(
11461146
parentNode: Element | Document,
11471147
tag: string,
11481148
props: Object,
1149+
index: number,
11491150
) {
11501151
if (__DEV__) {
11511152
if (didWarnInvalidHydration) {
11521153
return;
11531154
}
11541155
didWarnInvalidHydration = true;
1156+
let htmlContext = [];
1157+
const ic = parentNode.childNodes.length;
1158+
const parentNodeName = parentNode.nodeName.toLowerCase();
1159+
htmlContext.push(
1160+
' <' +
1161+
parentNodeName +
1162+
(parentNode.className
1163+
? ' className="' + parentNode.className + '"'
1164+
: '') +
1165+
'>',
1166+
);
1167+
if (index - 5 > 0) {
1168+
htmlContext.push(' …');
1169+
}
1170+
for (let i = index - 5; i <= index + 5; ++i) {
1171+
if (i >= 0 && i < ic) {
1172+
const childNode = parentNode.childNodes[i];
1173+
const childNodeName = childNode.nodeName.toLowerCase();
1174+
htmlContext.push(
1175+
(i === index ? '- ' : ' ') +
1176+
(childNode.nodeType === 1
1177+
? '<' +
1178+
childNodeName +
1179+
(childNode.className
1180+
? ' className="' + childNode.className + '"'
1181+
: '') +
1182+
(childNode.textContent
1183+
? '>' + childNode.textContent + '</' + childNodeName + '>'
1184+
: ' />')
1185+
: childNode.textContent),
1186+
);
1187+
if (i === index) {
1188+
htmlContext.push(
1189+
'+ <' +
1190+
tag +
1191+
(props.className ? ' className="' + props.className + '"' : '') +
1192+
' />',
1193+
);
1194+
}
1195+
}
1196+
}
1197+
if (index + 5 < ic - 1) {
1198+
htmlContext.push(' …');
1199+
}
1200+
htmlContext.push(' </' + parentNodeName + '>');
11551201
warning(
11561202
false,
1157-
'Expected server HTML to contain a matching <%s> in <%s>.%s',
1203+
'Expected server HTML to contain a matching <%s> in <%s>.%s%s',
11581204
tag,
1159-
parentNode.nodeName.toLowerCase(),
1205+
parentNodeName,
1206+
'\n' + htmlContext.join('\n') + '\n',
11601207
getStack(),
11611208
);
11621209
}

packages/react-reconciler/src/ReactFiberHydrationContext.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,7 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
168168
parentInstance,
169169
type,
170170
props,
171+
fiber.index,
171172
);
172173
break;
173174
case HostText:

packages/react-reconciler/src/ReactFiberReconciler.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,7 @@ type HydrationHostConfig<T, P, I, TI, HI, C, CX, PL> = {
206206
parentInstance: I,
207207
type: T,
208208
props: P,
209+
index: number,
209210
): void,
210211
didNotFindHydratableTextInstance(
211212
parentType: T,

scripts/rollup/results.json

Lines changed: 26 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -46,22 +46,22 @@
4646
"filename": "react-dom.development.js",
4747
"bundleType": "UMD_DEV",
4848
"packageName": "react-dom",
49-
"size": 591567,
50-
"gzip": 138758
49+
"size": 592727,
50+
"gzip": 139090
5151
},
5252
{
5353
"filename": "react-dom.production.min.js",
5454
"bundleType": "UMD_PROD",
5555
"packageName": "react-dom",
56-
"size": 96778,
57-
"gzip": 31445
56+
"size": 96782,
57+
"gzip": 31444
5858
},
5959
{
6060
"filename": "react-dom.development.js",
6161
"bundleType": "NODE_DEV",
6262
"packageName": "react-dom",
63-
"size": 575580,
64-
"gzip": 134533
63+
"size": 576740,
64+
"gzip": 134864
6565
},
6666
{
6767
"filename": "react-dom.production.min.js",
@@ -74,8 +74,8 @@
7474
"filename": "ReactDOM-dev.js",
7575
"bundleType": "FB_DEV",
7676
"packageName": "react-dom",
77-
"size": 594873,
78-
"gzip": 136788
77+
"size": 596363,
78+
"gzip": 137178
7979
},
8080
{
8181
"filename": "ReactDOM-prod.js",
@@ -221,8 +221,8 @@
221221
"filename": "react-art.development.js",
222222
"bundleType": "UMD_DEV",
223223
"packageName": "react-art",
224-
"size": 389869,
225-
"gzip": 86413
224+
"size": 389882,
225+
"gzip": 86423
226226
},
227227
{
228228
"filename": "react-art.production.min.js",
@@ -235,8 +235,8 @@
235235
"filename": "react-art.development.js",
236236
"bundleType": "NODE_DEV",
237237
"packageName": "react-art",
238-
"size": 313942,
239-
"gzip": 67385
238+
"size": 313955,
239+
"gzip": 67392
240240
},
241241
{
242242
"filename": "react-art.production.min.js",
@@ -249,8 +249,8 @@
249249
"filename": "ReactART-dev.js",
250250
"bundleType": "FB_DEV",
251251
"packageName": "react-art",
252-
"size": 318024,
253-
"gzip": 66603
252+
"size": 318053,
253+
"gzip": 66612
254254
},
255255
{
256256
"filename": "ReactART-prod.js",
@@ -263,8 +263,8 @@
263263
"filename": "ReactNativeRenderer-dev.js",
264264
"bundleType": "RN_DEV",
265265
"packageName": "react-native-renderer",
266-
"size": 443941,
267-
"gzip": 97414
266+
"size": 443970,
267+
"gzip": 97423
268268
},
269269
{
270270
"filename": "ReactNativeRenderer-prod.js",
@@ -277,8 +277,8 @@
277277
"filename": "react-test-renderer.development.js",
278278
"bundleType": "NODE_DEV",
279279
"packageName": "react-test-renderer",
280-
"size": 311062,
281-
"gzip": 66359
280+
"size": 311075,
281+
"gzip": 66366
282282
},
283283
{
284284
"filename": "react-test-renderer.production.min.js",
@@ -291,8 +291,8 @@
291291
"filename": "ReactTestRenderer-dev.js",
292292
"bundleType": "FB_DEV",
293293
"packageName": "react-test-renderer",
294-
"size": 315158,
295-
"gzip": 65549
294+
"size": 315187,
295+
"gzip": 65558
296296
},
297297
{
298298
"filename": "react-test-renderer-shallow.development.js",
@@ -333,8 +333,8 @@
333333
"filename": "react-reconciler.development.js",
334334
"bundleType": "NODE_DEV",
335335
"packageName": "react-reconciler",
336-
"size": 292377,
337-
"gzip": 61765
336+
"size": 292390,
337+
"gzip": 61771
338338
},
339339
{
340340
"filename": "react-reconciler.production.min.js",
@@ -375,8 +375,8 @@
375375
"filename": "ReactFabric-dev.js",
376376
"bundleType": "RN_DEV",
377377
"packageName": "react-native-renderer",
378-
"size": 438218,
379-
"gzip": 96267
378+
"size": 438247,
379+
"gzip": 96276
380380
},
381381
{
382382
"filename": "ReactFabric-prod.js",
@@ -389,8 +389,8 @@
389389
"filename": "react-reconciler-persistent.development.js",
390390
"bundleType": "NODE_DEV",
391391
"packageName": "react-reconciler",
392-
"size": 291949,
393-
"gzip": 61587
392+
"size": 291962,
393+
"gzip": 61594
394394
},
395395
{
396396
"filename": "react-reconciler-persistent.production.min.js",

0 commit comments

Comments
 (0)