Skip to content

Commit 7d11bf3

Browse files
committed
Compute hydrationWarningHostInstanceIndex by traversing the fiber tree
1 parent a41d037 commit 7d11bf3

File tree

3 files changed

+227
-29
lines changed

3 files changed

+227
-29
lines changed

fixtures/ssr/src/components/SSRMismatchTest.js

Lines changed: 131 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -285,6 +285,132 @@ const testCases = [
285285
</div>
286286
),
287287
},
288+
{
289+
key:
290+
'ssr-hydrationWarningHostInstanceIndex-didNotFindHydratableInstance-replacement',
291+
render: isServer => {
292+
class TestPaddingBeforeInnerComponent extends React.Component {
293+
render() {
294+
return (
295+
<React.Fragment>
296+
<div data-ssr-mismatch-padding-before="2" />
297+
<div data-ssr-mismatch-padding-before="3" />
298+
</React.Fragment>
299+
);
300+
}
301+
}
302+
class TestPaddingBeforeComponent extends React.Component {
303+
render() {
304+
return (
305+
<React.Fragment>
306+
<div data-ssr-mismatch-padding-before="1" />
307+
<TestPaddingBeforeInnerComponent />
308+
<div data-ssr-mismatch-padding-before="4" />
309+
<div data-ssr-mismatch-padding-before="5" />
310+
</React.Fragment>
311+
);
312+
}
313+
}
314+
class TestPaddingAfterComponent extends React.Component {
315+
render() {
316+
return (
317+
<React.Fragment>
318+
<div data-ssr-mismatch-padding-after="1" />
319+
<div data-ssr-mismatch-padding-after="2" />
320+
<div data-ssr-mismatch-padding-after="3" />
321+
<div data-ssr-mismatch-padding-after="4" />
322+
<div data-ssr-mismatch-padding-after="5" />
323+
</React.Fragment>
324+
);
325+
}
326+
}
327+
class TestNestedComponent extends React.Component {
328+
render() {
329+
if (this.props.isServer) {
330+
return (
331+
<div>
332+
<TestPaddingBeforeComponent />
333+
<h1>SSRMismatchTest default text</h1>
334+
<span />
335+
<TestPaddingAfterComponent />
336+
</div>
337+
);
338+
}
339+
return (
340+
<div>
341+
<TestPaddingBeforeComponent />
342+
<h2>SSRMismatchTest default text</h2>
343+
<span />
344+
<TestPaddingAfterComponent />
345+
</div>
346+
);
347+
}
348+
}
349+
class TestComponent extends React.Component {
350+
render() {
351+
return <TestNestedComponent isServer={this.props.isServer} />;
352+
}
353+
}
354+
355+
return <TestComponent isServer={isServer} />;
356+
},
357+
},
358+
359+
{
360+
key:
361+
'ssr-hydrationWarningHostInstanceIndex-didNotFindHydratableInstance-insertion',
362+
render(isServer) {
363+
class TestPaddingBeforeInnerInnerComponent extends React.Component {
364+
render() {
365+
return <div data-ssr-mismatch-padding-before="6" />;
366+
}
367+
}
368+
class TestPaddingBeforeInnerComponent extends React.Component {
369+
render() {
370+
return (
371+
<React.Fragment>
372+
<div data-ssr-mismatch-padding-before="4" />
373+
<div data-ssr-mismatch-padding-before="5" />
374+
<TestPaddingBeforeInnerInnerComponent />
375+
</React.Fragment>
376+
);
377+
}
378+
}
379+
class TestPaddingBeforeComponent extends React.Component {
380+
render() {
381+
return (
382+
<React.Fragment>
383+
<div data-ssr-mismatch-padding-before="2" />
384+
<div data-ssr-mismatch-padding-before="3" />
385+
<TestPaddingBeforeInnerComponent />
386+
<div data-ssr-mismatch-padding-before="7" />
387+
<div data-ssr-mismatch-padding-before="8" />
388+
<div data-ssr-mismatch-padding-before="9" />
389+
</React.Fragment>
390+
);
391+
}
392+
}
393+
394+
return isServer ? (
395+
<div>
396+
<div data-ssr-mismatch-padding-before="1" />
397+
<TestPaddingBeforeComponent />
398+
<div data-ssr-mismatch-padding-before="10" />
399+
<div data-ssr-mismatch-padding-before="11" />
400+
<div data-ssr-mismatch-padding-before="12" />
401+
</div>
402+
) : (
403+
<div>
404+
<div data-ssr-mismatch-padding-before="1" />
405+
<TestPaddingBeforeComponent />
406+
<div data-ssr-mismatch-padding-before="10" />
407+
<div data-ssr-mismatch-padding-before="11" />
408+
<div data-ssr-mismatch-padding-before="12" />
409+
SSRMismatchTest client text
410+
</div>
411+
);
412+
},
413+
},
288414
];
289415

290416
// Triggers the DOM mismatch warnings if requested via query string.
@@ -297,13 +423,14 @@ export default class SSRMismatchTest extends Component {
297423
);
298424
if (testCaseFound) {
299425
// In the browser where `window` is available, triggering a DOM mismatch if it's requested.
426+
const isServer = typeof window === 'undefined';
300427
let render;
301-
if (typeof window !== 'undefined') {
302-
render = testCaseFound.renderBrowser || testCaseFound.render;
303-
} else {
428+
if (isServer) {
304429
render = testCaseFound.renderServer || testCaseFound.render;
430+
} else {
431+
render = testCaseFound.renderBrowser || testCaseFound.render;
305432
}
306-
content = render();
433+
content = render(isServer);
307434
}
308435

309436
return (

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

Lines changed: 72 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -663,13 +663,24 @@ describe('ReactMount', () => {
663663
});
664664

665665
it('should warn when hydrate replaces an element within server-rendered nested components (replacement diff)', () => {
666-
class TestPaddingBeforeComponent extends React.Component {
666+
// See fixtures/ssr: ssr-hydrationWarningHostInstanceIndex-didNotFindHydratableInstance-replacement
667+
668+
class TestPaddingBeforeInnerComponent extends React.Component {
667669
render() {
668670
return (
669671
<React.Fragment>
670-
<div data-ssr-mismatch-padding-before="1" />
671672
<div data-ssr-mismatch-padding-before="2" />
672673
<div data-ssr-mismatch-padding-before="3" />
674+
</React.Fragment>
675+
);
676+
}
677+
}
678+
class TestPaddingBeforeComponent extends React.Component {
679+
render() {
680+
return (
681+
<React.Fragment>
682+
<div data-ssr-mismatch-padding-before="1" />
683+
<TestPaddingBeforeInnerComponent />
673684
<div data-ssr-mismatch-padding-before="4" />
674685
<div data-ssr-mismatch-padding-before="5" />
675686
</React.Fragment>
@@ -691,7 +702,7 @@ describe('ReactMount', () => {
691702
}
692703
class TestNestedComponent extends React.Component {
693704
render() {
694-
if (this.props.server) {
705+
if (this.props.isServer) {
695706
return (
696707
<div>
697708
<TestPaddingBeforeComponent />
@@ -713,18 +724,18 @@ describe('ReactMount', () => {
713724
}
714725
class TestComponent extends React.Component {
715726
render() {
716-
return <TestNestedComponent server={this.props.server} />;
727+
return <TestNestedComponent isServer={this.props.isServer} />;
717728
}
718729
}
719730

720731
const div = document.createElement('div');
721732
const markup = ReactDOMServer.renderToString(
722-
<TestComponent server={true} />,
733+
<TestComponent isServer={true} />,
723734
);
724735
div.innerHTML = markup;
725736

726737
expect(() =>
727-
ReactDOM.hydrate(<TestComponent server={false} />, div),
738+
ReactDOM.hydrate(<TestComponent isServer={false} />, div),
728739
).toWarnDev(
729740
'Warning: Expected server HTML to contain a matching <h2> in <div>.\n\n' +
730741
' <div data-reactroot="">\n' +
@@ -894,27 +905,60 @@ describe('ReactMount', () => {
894905
);
895906
});
896907

897-
it('should warn when hydrate inserts a text node between matching elements (insertion diff)', () => {
898-
// See fixtures/ssr: ssr-warnForInsertedHydratedText-didNotFindHydratableTextInstance
908+
it('should warn when hydrate inserts a text node after matching elements (insertion diff)', () => {
909+
// See fixtures/ssr: ssr-hydrationWarningHostInstanceIndex-didNotFindHydratableInstance-insertion
910+
911+
class TestPaddingBeforeInnerInnerComponent extends React.Component {
912+
render() {
913+
return <div data-ssr-mismatch-padding-before="6" />;
914+
}
915+
}
916+
class TestPaddingBeforeInnerComponent extends React.Component {
917+
render() {
918+
return (
919+
<React.Fragment>
920+
<div data-ssr-mismatch-padding-before="4" />
921+
<div data-ssr-mismatch-padding-before="5" />
922+
<TestPaddingBeforeInnerInnerComponent />
923+
</React.Fragment>
924+
);
925+
}
926+
}
927+
class TestPaddingBeforeComponent extends React.Component {
928+
render() {
929+
return (
930+
<React.Fragment>
931+
<div data-ssr-mismatch-padding-before="2" />
932+
<div data-ssr-mismatch-padding-before="3" />
933+
<TestPaddingBeforeInnerComponent />
934+
<div data-ssr-mismatch-padding-before="7" />
935+
<div data-ssr-mismatch-padding-before="8" />
936+
<div data-ssr-mismatch-padding-before="9" />
937+
</React.Fragment>
938+
);
939+
}
940+
}
899941

900942
const div = document.createElement('div');
901943
const markup = ReactDOMServer.renderToString(
902944
<div>
903-
<span data-ssr-mismatch-padding-before="1" />
904-
<span />
905-
<div data-ssr-mismatch-padding-after="1" />
906-
<div data-ssr-mismatch-padding-after="2" />
907-
<div data-ssr-mismatch-padding-after="3" />
908-
<div data-ssr-mismatch-padding-after="4" />
909-
<div data-ssr-mismatch-padding-after="5" />
945+
<div data-ssr-mismatch-padding-before="1" />
946+
<TestPaddingBeforeComponent />
947+
<div data-ssr-mismatch-padding-before="10" />
948+
<div data-ssr-mismatch-padding-before="11" />
949+
<div data-ssr-mismatch-padding-before="12" />
910950
</div>,
911951
);
912952
div.innerHTML = markup;
913953

914954
expect(() =>
915955
ReactDOM.hydrate(
916956
<div>
917-
<span data-ssr-mismatch-padding-before="1" />
957+
<div data-ssr-mismatch-padding-before="1" />
958+
<TestPaddingBeforeComponent />
959+
<div data-ssr-mismatch-padding-before="10" />
960+
<div data-ssr-mismatch-padding-before="11" />
961+
<div data-ssr-mismatch-padding-before="12" />
918962
SSRMismatchTest client text
919963
</div>,
920964
div,
@@ -923,14 +967,19 @@ describe('ReactMount', () => {
923967
'Warning: Expected server HTML to contain a matching text node' +
924968
" for {'SSRMismatchTest client text'} in <div>.\n\n" +
925969
' <div data-reactroot="">\n' +
926-
' <span data-ssr-mismatch-padding-before="1"></span>\n' +
970+
' <div data-ssr-mismatch-padding-before="1"></div>\n' +
971+
' <div data-ssr-mismatch-padding-before="2"></div>\n' +
972+
' <div data-ssr-mismatch-padding-before="3"></div>\n' +
973+
' <div data-ssr-mismatch-padding-before="4"></div>\n' +
974+
' <div data-ssr-mismatch-padding-before="5"></div>\n' +
975+
' <div data-ssr-mismatch-padding-before="6"></div>\n' +
976+
' <div data-ssr-mismatch-padding-before="7"></div>\n' +
977+
' <div data-ssr-mismatch-padding-before="8"></div>\n' +
978+
' <div data-ssr-mismatch-padding-before="9"></div>\n' +
979+
' <div data-ssr-mismatch-padding-before="10"></div>\n' +
980+
' <div data-ssr-mismatch-padding-before="11"></div>\n' +
981+
' <div data-ssr-mismatch-padding-before="12"></div>\n' +
927982
"+ {'SSRMismatchTest client text'}\n" +
928-
' <span></span>\n' +
929-
' <div data-ssr-mismatch-padding-after="1"></div>\n' +
930-
' <div data-ssr-mismatch-padding-after="2"></div>\n' +
931-
' <div data-ssr-mismatch-padding-after="3"></div>\n' +
932-
' <div data-ssr-mismatch-padding-after="4"></div>\n' +
933-
' <div data-ssr-mismatch-padding-after="5"></div>\n' +
934983
' </div>\n\n' +
935984
' in div (at **)',
936985
);

packages/react-reconciler/src/ReactFiberHydrationContext.js

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -106,8 +106,30 @@ function insertNonHydratedInstance(
106106
) {
107107
fiber.effectTag |= Placement;
108108
if (__DEV__) {
109-
// TODO: Find out what `hydrationWarningHostInstanceIndex` should be, `fiber.index` is wrong.
110-
const hydrationWarningHostInstanceIndex = fiber.index;
109+
let hydrationWarningHostInstanceIndex = 0;
110+
{
111+
// Count rendered host nodes by traversing `returnFiber` subtree until `fiber` is found.
112+
let node = returnFiber.child;
113+
const nextNodeStack = [];
114+
while (node && node !== fiber) {
115+
if (node.tag === HostComponent || node.tag === HostText) {
116+
++hydrationWarningHostInstanceIndex;
117+
}
118+
// Depth-first traversal.
119+
if (node.child) {
120+
if (node.sibling) {
121+
// Remember where to continue on this tree level, then go deeper.
122+
nextNodeStack.push(node.sibling);
123+
}
124+
node = node.child;
125+
} else if (node.sibling) {
126+
node = node.sibling;
127+
} else {
128+
node = nextNodeStack.pop();
129+
}
130+
}
131+
}
132+
111133
switch (returnFiber.tag) {
112134
case HostRoot: {
113135
const parentContainer = returnFiber.stateNode.containerInfo;

0 commit comments

Comments
 (0)