Give context aware message if markup reuse fails
The context is an extra message with comparison of where server and client markup started to differ.
Árni Hermann Reynisson committed
Nov 30, 2014 at 16:27 UTC
a352b94f57150e0e84fd1d92f1aebb7d5f422890
2 files changed
+40
-6
src/browser/ui/ReactMount.js
+37
-5
@@ -54,6 +54,22 @@ if (__DEV__) {
54
// Used to store breadth-first search state in findComponentRoot.
55
var findComponentRootReusableArray = [];
56
57
+/**
58
+ * Finds the index of the first character
59
+ * that's not common between the two given strings.
60
+ *
61
+ * @return {number} the index of the character where the strings diverge
62
+ */
63
+function firstDifferenceIndex(string1, string2) {
64
+ var minLen = Math.min(string1.length, string2.length);
65
+ for (var i = 0; i < minLen; i++) {
66
+ if (string1[i] !== string2[i]) {
67
+ return i;
68
+ }
69
+ }
70
+ return string1.length === string2.length ? -1 : minLen;
71
+}
72
+
73
/**
74
* @param {DOMElement} container DOM element that may contain a React component.
75
* @return {?string} A "reactRoot" ID, if a React component is rendered.
@@ -719,11 +735,26 @@ var ReactMount = {
735
);
736
737
if (shouldReuseMarkup) {
722
- if (ReactMarkupChecksum.canReuseMarkup(
723
- markup,
724
- getReactRootElementInContainer(container))) {
738
+ var rootElement = getReactRootElementInContainer(container);
739
+ if (ReactMarkupChecksum.canReuseMarkup(markup, rootElement)) {
740
return;
741
} else {
742
+ var checksum = rootElement.getAttribute(
743
+ ReactMarkupChecksum.CHECKSUM_ATTR_NAME
744
+ );
745
+ rootElement.removeAttribute(ReactMarkupChecksum.CHECKSUM_ATTR_NAME);
746
+
747
+ var rootMarkup = rootElement.outerHTML;
748
+ rootElement.setAttribute(
749
+ ReactMarkupChecksum.CHECKSUM_ATTR_NAME,
750
+ checksum
751
+ );
752
+
753
+ var diffIndex = firstDifferenceIndex(markup, rootMarkup);
754
+ var difference = ' (client) ' +
755
+ markup.substring(diffIndex - 20, diffIndex + 20) +
756
+ '\n (server) ' + rootMarkup.substring(diffIndex - 20, diffIndex + 20);
757
+
758
invariant(
759
container.nodeType !== DOC_NODE_TYPE,
760
'You\'re trying to render a component to the document using ' +
@@ -733,7 +764,8 @@ var ReactMount = {
764
'methods are impure. React cannot handle this case due to ' +
765
'cross-browser quirks by rendering at the document root. You ' +
766
'should look for environment dependent code in your components ' +
736
- 'and ensure the props are the same client and server side.'
767
+ 'and ensure the props are the same client and server side:\n' +
768
+ difference
769
);
770
771
if (__DEV__) {
@@ -745,7 +777,7 @@ var ReactMount = {
777
'new markup to compensate which works but you have lost many ' +
778
'of the benefits of server rendering. Instead, figure out ' +
779
'why the markup being generated is different on the client ' +
748
- 'or server.'
780
+ 'or server:\n' + difference
781
);
782
}
783
}
src/browser/ui/__tests__/ReactRenderDocument-test.js
+3
-1
@@ -210,7 +210,9 @@ describe('rendering React components at document', function() {
210
'are impure. React cannot handle this case due to cross-browser ' +
211
'quirks by rendering at the document root. You should look for ' +
212
'environment dependent code in your components and ensure ' +
213
- 'the props are the same client and server side.'
213
+ 'the props are the same client and server side:\n' +
214
+ ' (client) data-reactid=".0.1">Hello world</body></\n' +
215
+ ' (server) data-reactid=".0.1">Goodbye world</body>'
216
);
217
});
218