Skip to content

Commit c022a52

Browse files
committed
Change hydration warning index algorithm to use .return pointers, not stack
The stack was required to track where to return after child siblings traversal. The `fiber.return` pointers were made for that purpose, it's safe to overwrite them because they are only used to traverse the fiber tree, and their values outside the traversal functions are not relied upon. One test fails for yet unknown reason, the text node is inserted too early: ``` ● ReactMount › should warn when hydrate inserts a text node after matching elements (insertion diff) Error: Unexpected warning recorded: - Expected + Received Warning: Expected server HTML to contain a matching text node for {'SSRMismatchTest client text'} in <div>. <div data-reactroot=""> <div data-ssr-mismatch-padding-before="1"><span></span></div> <div data-ssr-mismatch-padding-before="2"></div> + + {'SSRMismatchTest client text'} <div data-ssr-mismatch-padding-before="3"></div> <div data-ssr-mismatch-padding-before="4"></div> <div data-ssr-mismatch-padding-before="5"></div> <div data-ssr-mismatch-padding-before="6"></div> <div data-ssr-mismatch-padding-before="7"></div> <div data-ssr-mismatch-padding-before="8"></div> <div data-ssr-mismatch-padding-before="9"></div> <div data-ssr-mismatch-padding-before="10"></div> <div data-ssr-mismatch-padding-before="11"></div> <div data-ssr-mismatch-padding-before="12"></div> <div data-ssr-mismatch-padding-before="13"></div> - + {'SSRMismatchTest client text'} </div> in div (at **) ```
1 parent 7d11bf3 commit c022a52

File tree

2 files changed

+48
-30
lines changed

2 files changed

+48
-30
lines changed

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

Lines changed: 25 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -679,7 +679,9 @@ describe('ReactMount', () => {
679679
render() {
680680
return (
681681
<React.Fragment>
682-
<div data-ssr-mismatch-padding-before="1" />
682+
<div data-ssr-mismatch-padding-before="1">
683+
<span />
684+
</div>
683685
<TestPaddingBeforeInnerComponent />
684686
<div data-ssr-mismatch-padding-before="4" />
685687
<div data-ssr-mismatch-padding-before="5" />
@@ -739,7 +741,7 @@ describe('ReactMount', () => {
739741
).toWarnDev(
740742
'Warning: Expected server HTML to contain a matching <h2> in <div>.\n\n' +
741743
' <div data-reactroot="">\n' +
742-
' <div data-ssr-mismatch-padding-before="1"></div>\n' +
744+
' <div data-ssr-mismatch-padding-before="1"><span></span></div>\n' +
743745
' <div data-ssr-mismatch-padding-before="2"></div>\n' +
744746
' <div data-ssr-mismatch-padding-before="3"></div>\n' +
745747
' <div data-ssr-mismatch-padding-before="4"></div>\n' +
@@ -910,15 +912,15 @@ describe('ReactMount', () => {
910912

911913
class TestPaddingBeforeInnerInnerComponent extends React.Component {
912914
render() {
913-
return <div data-ssr-mismatch-padding-before="6" />;
915+
return <div data-ssr-mismatch-padding-before="8" />;
914916
}
915917
}
916918
class TestPaddingBeforeInnerComponent extends React.Component {
917919
render() {
918920
return (
919921
<React.Fragment>
920-
<div data-ssr-mismatch-padding-before="4" />
921-
<div data-ssr-mismatch-padding-before="5" />
922+
<div data-ssr-mismatch-padding-before="6" />
923+
<div data-ssr-mismatch-padding-before="7" />
922924
<TestPaddingBeforeInnerInnerComponent />
923925
</React.Fragment>
924926
);
@@ -928,12 +930,11 @@ describe('ReactMount', () => {
928930
render() {
929931
return (
930932
<React.Fragment>
931-
<div data-ssr-mismatch-padding-before="2" />
932-
<div data-ssr-mismatch-padding-before="3" />
933+
<div data-ssr-mismatch-padding-before="4" />
934+
<div data-ssr-mismatch-padding-before="5" />
933935
<TestPaddingBeforeInnerComponent />
934-
<div data-ssr-mismatch-padding-before="7" />
935-
<div data-ssr-mismatch-padding-before="8" />
936936
<div data-ssr-mismatch-padding-before="9" />
937+
<div data-ssr-mismatch-padding-before="10" />
937938
</React.Fragment>
938939
);
939940
}
@@ -942,32 +943,40 @@ describe('ReactMount', () => {
942943
const div = document.createElement('div');
943944
const markup = ReactDOMServer.renderToString(
944945
<div>
945-
<div data-ssr-mismatch-padding-before="1" />
946+
<div data-ssr-mismatch-padding-before="1">
947+
<span />
948+
</div>
949+
<div data-ssr-mismatch-padding-before="2" />
950+
<div data-ssr-mismatch-padding-before="3" />
946951
<TestPaddingBeforeComponent />
947-
<div data-ssr-mismatch-padding-before="10" />
948952
<div data-ssr-mismatch-padding-before="11" />
949953
<div data-ssr-mismatch-padding-before="12" />
954+
<div data-ssr-mismatch-padding-before="13" />
950955
</div>,
951956
);
952957
div.innerHTML = markup;
953958

954959
expect(() =>
955960
ReactDOM.hydrate(
956961
<div>
957-
<div data-ssr-mismatch-padding-before="1" />
962+
<div data-ssr-mismatch-padding-before="1">
963+
<span />
964+
</div>
965+
<div data-ssr-mismatch-padding-before="2" />
966+
<div data-ssr-mismatch-padding-before="3" />
958967
<TestPaddingBeforeComponent />
959-
<div data-ssr-mismatch-padding-before="10" />
960968
<div data-ssr-mismatch-padding-before="11" />
961969
<div data-ssr-mismatch-padding-before="12" />
962-
SSRMismatchTest client text
970+
<div data-ssr-mismatch-padding-before="13" />
971+
{'SSRMismatchTest client text'}
963972
</div>,
964973
div,
965974
),
966975
).toWarnDev(
967976
'Warning: Expected server HTML to contain a matching text node' +
968977
" for {'SSRMismatchTest client text'} in <div>.\n\n" +
969978
' <div data-reactroot="">\n' +
970-
' <div data-ssr-mismatch-padding-before="1"></div>\n' +
979+
' <div data-ssr-mismatch-padding-before="1"><span></span></div>\n' +
971980
' <div data-ssr-mismatch-padding-before="2"></div>\n' +
972981
' <div data-ssr-mismatch-padding-before="3"></div>\n' +
973982
' <div data-ssr-mismatch-padding-before="4"></div>\n' +
@@ -979,6 +988,7 @@ describe('ReactMount', () => {
979988
' <div data-ssr-mismatch-padding-before="10"></div>\n' +
980989
' <div data-ssr-mismatch-padding-before="11"></div>\n' +
981990
' <div data-ssr-mismatch-padding-before="12"></div>\n' +
991+
' <div data-ssr-mismatch-padding-before="13"></div>\n' +
982992
"+ {'SSRMismatchTest client text'}\n" +
983993
' </div>\n\n' +
984994
' in div (at **)',

packages/react-reconciler/src/ReactFiberHydrationContext.js

Lines changed: 23 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,12 @@ import type {
1616
HostContext,
1717
} from './ReactFiberHostConfig';
1818

19-
import {HostComponent, HostText, HostRoot} from 'shared/ReactWorkTags';
19+
import {
20+
HostComponent,
21+
HostText,
22+
HostRoot,
23+
HostPortal,
24+
} from 'shared/ReactWorkTags';
2025
import {Deletion, Placement} from 'shared/ReactSideEffectTags';
2126
import invariant from 'shared/invariant';
2227

@@ -108,25 +113,28 @@ function insertNonHydratedInstance(
108113
if (__DEV__) {
109114
let hydrationWarningHostInstanceIndex = 0;
110115
{
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) {
116+
// Find index of `fiber`, the place where hydration failed, among immediate children host nodes of `returnFiber`.
117+
const startNode = returnFiber.child;
118+
let node: Fiber = startNode;
119+
search: while (node !== fiber) {
115120
if (node.tag === HostComponent || node.tag === HostText) {
116121
++hydrationWarningHostInstanceIndex;
122+
} else if (node.tag === HostPortal) {
123+
// Do not count HostPortal and do not descend into them as they do not affect the index within the parent.
124+
} else if (node.child !== null) {
125+
// Do not descend into HostComponent or HostText as they do not affect the index within the parent.
126+
node.child.return = node;
127+
node = node.child;
128+
continue;
117129
}
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);
130+
while (node.sibling === null) {
131+
if (node.return === null || node.return === startNode) {
132+
break search;
123133
}
124-
node = node.child;
125-
} else if (node.sibling) {
126-
node = node.sibling;
127-
} else {
128-
node = nextNodeStack.pop();
134+
node = node.return;
129135
}
136+
node.sibling.return = node.return;
137+
node = node.sibling;
130138
}
131139
}
132140

0 commit comments

Comments
 (0)