diff --git a/src/browser/ReactTextComponent.js b/src/browser/ReactTextComponent.js index e23c720232180..58d1f64f82cae 100644 --- a/src/browser/ReactTextComponent.js +++ b/src/browser/ReactTextComponent.js @@ -33,8 +33,9 @@ var mixInto = require('mixInto'); * - When mounting text into the DOM, adjacent text nodes are merged. * - Text nodes cannot be assigned a React root ID. * - * This component is used to wrap strings in elements so that they can undergo - * the same reconciliation that is applied to elements. + * This component is used to add a ' + escapedText ); }, @@ -95,7 +95,7 @@ mixInto(ReactTextComponent, { var nextProps = nextComponent.props; if (nextProps !== this.props) { this.props = nextProps; - ReactComponent.BackendIDOperations.updateTextContentByID( + ReactComponent.BackendIDOperations.updateTextContentAfterByID( this._rootNodeID, nextProps ); diff --git a/src/browser/server/__tests__/ReactServerRendering-test.js b/src/browser/server/__tests__/ReactServerRendering-test.js index 895ce51238e81..69b0968a4ae03 100644 --- a/src/browser/server/__tests__/ReactServerRendering-test.js +++ b/src/browser/server/__tests__/ReactServerRendering-test.js @@ -94,8 +94,8 @@ describe('ReactServerRendering', function() { '
' + '' + - 'My name is ' + - 'child' + + 'My name is ' + + 'child' + '' + '
' ); @@ -143,8 +143,10 @@ describe('ReactServerRendering', function() { expect(response).toMatch( '' + - 'Component name: ' + - 'TestComponent' + + '' + + 'Component name: ' + + '' + + 'TestComponent' + '' ); expect(lifecycle).toEqual( diff --git a/src/browser/ui/ReactDOMIDOperations.js b/src/browser/ui/ReactDOMIDOperations.js index ea206cd065b90..98456c4c47331 100644 --- a/src/browser/ui/ReactDOMIDOperations.js +++ b/src/browser/ui/ReactDOMIDOperations.js @@ -135,18 +135,18 @@ var ReactDOMIDOperations = { ), /** - * Updates a DOM node's text content set by `props.content`. + * Updates the text content after a DOM node. * * @param {string} id ID of the node to update. * @param {string} content Text content. * @internal */ - updateTextContentByID: ReactPerf.measure( + updateTextContentAfterByID: ReactPerf.measure( 'ReactDOMIDOperations', - 'updateTextContentByID', + 'updateTextContentAfterByID', function(id, content) { var node = ReactMount.getNode(id); - DOMChildrenOperations.updateTextContent(node, content); + DOMChildrenOperations.updateTextContentAfter(node, content); } ), diff --git a/src/browser/ui/dom/DOMChildrenOperations.js b/src/browser/ui/dom/DOMChildrenOperations.js index 0c53caf2c968f..46a42ebf6ea98 100644 --- a/src/browser/ui/dom/DOMChildrenOperations.js +++ b/src/browser/ui/dom/DOMChildrenOperations.js @@ -25,6 +25,8 @@ var ReactMultiChildUpdateTypes = require('ReactMultiChildUpdateTypes'); var getTextContentAccessor = require('getTextContentAccessor'); var invariant = require('invariant'); +var TEXT_NODE = 3; + /** * The DOM property to use when setting text content. * @@ -48,7 +50,7 @@ function insertChildAt(parentNode, childNode, index) { // browsers so we must replace it with `null`. parentNode.insertBefore( childNode, - parentNode.childNodes[index] || null + parentNode.children[index] || null ); } @@ -83,6 +85,16 @@ if (textContentAccessor === 'textContent') { }; } +function updateTextContentAfter(node, text) { + while (node.nextSibling && node.nextSibling.nodeType === TEXT_NODE) { + node.parentNode.removeChild(node.nextSibling); + } + if (text.length) { + var doc = node.ownerDocument || document; + node.parentNode.insertBefore(doc.createTextNode(text), node.nextSibling); + } +} + /** * Operations for updating with DOM children. */ @@ -90,7 +102,7 @@ var DOMChildrenOperations = { dangerouslyReplaceNodeWithMarkup: Danger.dangerouslyReplaceNodeWithMarkup, - updateTextContent: updateTextContent, + updateTextContentAfter: updateTextContentAfter, /** * Updates a component's children by processing a series of updates. The @@ -111,7 +123,7 @@ var DOMChildrenOperations = { if (update.type === ReactMultiChildUpdateTypes.MOVE_EXISTING || update.type === ReactMultiChildUpdateTypes.REMOVE_NODE) { var updatedIndex = update.fromIndex; - var updatedChild = update.parentNode.childNodes[updatedIndex]; + var updatedChild = update.parentNode.children[updatedIndex]; var parentID = update.parentID; invariant( @@ -140,7 +152,13 @@ var DOMChildrenOperations = { // Remove updated children first so that `toIndex` is consistent. if (updatedChildren) { for (var j = 0; j < updatedChildren.length; j++) { - updatedChildren[j].parentNode.removeChild(updatedChildren[j]); + var nodeToRemove = updatedChildren[j]; + // Remove trailing text nodes, probably from ReactTextComponent + while (nodeToRemove.nextSibling && + nodeToRemove.nextSibling.nodeType === TEXT_NODE) { + nodeToRemove.parentNode.removeChild(nodeToRemove.nextSibling); + } + nodeToRemove.parentNode.removeChild(nodeToRemove); } } diff --git a/src/browser/ui/dom/Danger.js b/src/browser/ui/dom/Danger.js index 6e2dd071056e9..4b42f8579a993 100644 --- a/src/browser/ui/dom/Danger.js +++ b/src/browser/ui/dom/Danger.js @@ -30,6 +30,7 @@ var invariant = require('invariant'); var OPEN_TAG_NAME_EXP = /^(<[^ \/>]+)/; var RESULT_INDEX_ATTR = 'data-danger-index'; +var TEXT_NODE = 3; /** * Extracts the `nodeName` from a string of markup. @@ -54,7 +55,7 @@ var Danger = { * `markupList` should be the same. * * @param {array} markupList List of markup strings to render. - * @return {array} List of rendered nodes. + * @return {array} List of rendered nodes. * @internal */ dangerouslyRenderMarkup: function(markupList) { @@ -109,6 +110,7 @@ var Danger = { emptyFunction // Do nothing special with