diff --git a/src/browser/ui/ReactMount.js b/src/browser/ui/ReactMount.js index 9e6050cc9cbad..6e72848ca1a06 100644 --- a/src/browser/ui/ReactMount.js +++ b/src/browser/ui/ReactMount.js @@ -54,6 +54,22 @@ if (__DEV__) { // Used to store breadth-first search state in findComponentRoot. var findComponentRootReusableArray = []; +/** + * Finds the index of the first character + * that's not common between the two given strings. + * + * @return {number} the index of the character where the strings diverge + */ +function firstDifferenceIndex(string1, string2) { + var minLen = Math.min(string1.length, string2.length); + for (var i = 0; i < minLen; i++) { + if (string1[i] !== string2[i]) { + return i; + } + } + return string1.length === string2.length ? -1 : minLen; +} + /** * @param {DOMElement} container DOM element that may contain a React component. * @return {?string} A "reactRoot" ID, if a React component is rendered. @@ -719,11 +735,26 @@ var ReactMount = { ); if (shouldReuseMarkup) { - if (ReactMarkupChecksum.canReuseMarkup( - markup, - getReactRootElementInContainer(container))) { + var rootElement = getReactRootElementInContainer(container); + if (ReactMarkupChecksum.canReuseMarkup(markup, rootElement)) { return; } else { + var checksum = rootElement.getAttribute( + ReactMarkupChecksum.CHECKSUM_ATTR_NAME + ); + rootElement.removeAttribute(ReactMarkupChecksum.CHECKSUM_ATTR_NAME); + + var rootMarkup = rootElement.outerHTML; + rootElement.setAttribute( + ReactMarkupChecksum.CHECKSUM_ATTR_NAME, + checksum + ); + + var diffIndex = firstDifferenceIndex(markup, rootMarkup); + var difference = ' (client) ' + + markup.substring(diffIndex - 20, diffIndex + 20) + + '\n (server) ' + rootMarkup.substring(diffIndex - 20, diffIndex + 20); + invariant( container.nodeType !== DOC_NODE_TYPE, 'You\'re trying to render a component to the document using ' + @@ -733,7 +764,8 @@ var ReactMount = { 'methods are impure. React cannot handle this case due to ' + 'cross-browser quirks by rendering at the document root. You ' + 'should look for environment dependent code in your components ' + - 'and ensure the props are the same client and server side.' + 'and ensure the props are the same client and server side:\n' + + difference ); if (__DEV__) { @@ -745,7 +777,7 @@ var ReactMount = { 'new markup to compensate which works but you have lost many ' + 'of the benefits of server rendering. Instead, figure out ' + 'why the markup being generated is different on the client ' + - 'or server.' + 'or server:\n' + difference ); } } diff --git a/src/browser/ui/__tests__/ReactRenderDocument-test.js b/src/browser/ui/__tests__/ReactRenderDocument-test.js index 6e18257fb4b80..2a9d9f3bedbf3 100644 --- a/src/browser/ui/__tests__/ReactRenderDocument-test.js +++ b/src/browser/ui/__tests__/ReactRenderDocument-test.js @@ -210,7 +210,9 @@ describe('rendering React components at document', function() { 'are impure. React cannot handle this case due to cross-browser ' + 'quirks by rendering at the document root. You should look for ' + 'environment dependent code in your components and ensure ' + - 'the props are the same client and server side.' + 'the props are the same client and server side:\n' + + ' (client) data-reactid=".0.1">Hello worldGoodbye world' ); });