diff --git a/src/renderers/dom/client/ReactDOMIDOperations.js b/src/renderers/dom/client/ReactDOMIDOperations.js
index 637035fcd8a1..bec8d3e6192a 100644
--- a/src/renderers/dom/client/ReactDOMIDOperations.js
+++ b/src/renderers/dom/client/ReactDOMIDOperations.js
@@ -19,7 +19,6 @@ var ReactMount = require('ReactMount');
var ReactPerf = require('ReactPerf');
var invariant = require('invariant');
-var setInnerHTML = require('setInnerHTML');
/**
* Errors for properties that should not be updated with `updatePropertyByID()`.
@@ -115,18 +114,6 @@ var ReactDOMIDOperations = {
CSSPropertyOperations.setValueForStyles(node, styles);
},
- /**
- * Updates a DOM node's innerHTML.
- *
- * @param {string} id ID of the node to update.
- * @param {string} html An HTML string.
- * @internal
- */
- updateInnerHTMLByID: function(id, html) {
- var node = ReactMount.getNode(id);
- setInnerHTML(node, html);
- },
-
/**
* Updates a DOM node's text content set by `props.content`.
*
@@ -171,7 +158,6 @@ ReactPerf.measureMethods(ReactDOMIDOperations, 'ReactDOMIDOperations', {
updatePropertyByID: 'updatePropertyByID',
deletePropertyByID: 'deletePropertyByID',
updateStylesByID: 'updateStylesByID',
- updateInnerHTMLByID: 'updateInnerHTMLByID',
updateTextContentByID: 'updateTextContentByID',
dangerouslyReplaceNodeWithMarkupByID: 'dangerouslyReplaceNodeWithMarkupByID',
dangerouslyProcessChildrenUpdates: 'dangerouslyProcessChildrenUpdates',
diff --git a/src/renderers/dom/client/__tests__/ReactDOMIDOperations-test.js b/src/renderers/dom/client/__tests__/ReactDOMIDOperations-test.js
index be744c7a5fd8..18eb7378a2ee 100644
--- a/src/renderers/dom/client/__tests__/ReactDOMIDOperations-test.js
+++ b/src/renderers/dom/client/__tests__/ReactDOMIDOperations-test.js
@@ -15,6 +15,7 @@ describe('ReactDOMIDOperations', function() {
var DOMPropertyOperations = require('DOMPropertyOperations');
var ReactDOMIDOperations = require('ReactDOMIDOperations');
var ReactMount = require('ReactMount');
+ var ReactMultiChildUpdateTypes = require('ReactMultiChildUpdateTypes');
var keyOf = require('keyOf');
it('should disallow updating special properties', function() {
@@ -44,9 +45,17 @@ describe('ReactDOMIDOperations', function() {
var html = '\n \t \n testContent \t \n \t';
- ReactDOMIDOperations.updateInnerHTMLByID(
- 'testID',
- html
+ ReactDOMIDOperations.dangerouslyProcessChildrenUpdates(
+ [{
+ parentID: 'testID',
+ parentNode: null,
+ type: ReactMultiChildUpdateTypes.SET_MARKUP,
+ markupIndex: null,
+ content: html,
+ fromIndex: null,
+ toIndex: null,
+ }],
+ []
);
expect(
diff --git a/src/renderers/dom/client/utils/DOMChildrenOperations.js b/src/renderers/dom/client/utils/DOMChildrenOperations.js
index cd739d875dba..ad6bea4057ba 100644
--- a/src/renderers/dom/client/utils/DOMChildrenOperations.js
+++ b/src/renderers/dom/client/utils/DOMChildrenOperations.js
@@ -15,6 +15,7 @@
var Danger = require('Danger');
var ReactMultiChildUpdateTypes = require('ReactMultiChildUpdateTypes');
+var setInnerHTML = require('setInnerHTML');
var setTextContent = require('setTextContent');
var invariant = require('invariant');
@@ -123,10 +124,16 @@ var DOMChildrenOperations = {
update.toIndex
);
break;
+ case ReactMultiChildUpdateTypes.SET_MARKUP:
+ setInnerHTML(
+ update.parentNode,
+ update.content
+ );
+ break;
case ReactMultiChildUpdateTypes.TEXT_CONTENT:
setTextContent(
update.parentNode,
- update.textContent
+ update.content
);
break;
case ReactMultiChildUpdateTypes.REMOVE_NODE:
diff --git a/src/renderers/dom/shared/ReactDOMComponent.js b/src/renderers/dom/shared/ReactDOMComponent.js
index 9f3af5cfc047..4ed845c5b8b0 100644
--- a/src/renderers/dom/shared/ReactDOMComponent.js
+++ b/src/renderers/dom/shared/ReactDOMComponent.js
@@ -884,10 +884,7 @@ ReactDOMComponent.Mixin = {
}
} else if (nextHtml != null) {
if (lastHtml !== nextHtml) {
- BackendIDOperations.updateInnerHTMLByID(
- this._rootNodeID,
- nextHtml
- );
+ this.updateMarkup('' + nextHtml);
}
} else if (nextChildren != null) {
this.updateChildren(nextChildren, transaction, context);
diff --git a/src/renderers/dom/shared/__tests__/ReactDOMComponent-test.js b/src/renderers/dom/shared/__tests__/ReactDOMComponent-test.js
index d658a862370c..445fd2f04b19 100644
--- a/src/renderers/dom/shared/__tests__/ReactDOMComponent-test.js
+++ b/src/renderers/dom/shared/__tests__/ReactDOMComponent-test.js
@@ -309,6 +309,30 @@ describe('ReactDOMComponent', function() {
expect(container.firstChild.innerHTML).toEqual('adieu');
});
+ it('should transition from innerHTML to children in nested el', function() {
+ var container = document.createElement('div');
+ React.render(
+
,
+ container
+ );
+
+ expect(container.textContent).toEqual('bonjour');
+ React.render(, container);
+ expect(container.textContent).toEqual('adieu');
+ });
+
+ it('should transition from children to innerHTML in nested el', function() {
+ var container = document.createElement('div');
+ React.render(, container);
+
+ expect(container.textContent).toEqual('adieu');
+ React.render(
+ ,
+ container
+ );
+ expect(container.textContent).toEqual('bonjour');
+ });
+
it('should not incur unnecessary DOM mutations', function() {
var container = document.createElement('div');
React.render(, container);
diff --git a/src/renderers/shared/reconciler/ReactMultiChild.js b/src/renderers/shared/reconciler/ReactMultiChild.js
index af5e42f503ba..3ae6ddd790b1 100644
--- a/src/renderers/shared/reconciler/ReactMultiChild.js
+++ b/src/renderers/shared/reconciler/ReactMultiChild.js
@@ -53,14 +53,14 @@ var markupQueue = [];
* @param {number} toIndex Destination index.
* @private
*/
-function enqueueMarkup(parentID, markup, toIndex) {
+function enqueueInsertMarkup(parentID, markup, toIndex) {
// NOTE: Null values reduce hidden classes.
updateQueue.push({
parentID: parentID,
parentNode: null,
type: ReactMultiChildUpdateTypes.INSERT_MARKUP,
markupIndex: markupQueue.push(markup) - 1,
- textContent: null,
+ content: null,
fromIndex: null,
toIndex: toIndex,
});
@@ -81,7 +81,7 @@ function enqueueMove(parentID, fromIndex, toIndex) {
parentNode: null,
type: ReactMultiChildUpdateTypes.MOVE_EXISTING,
markupIndex: null,
- textContent: null,
+ content: null,
fromIndex: fromIndex,
toIndex: toIndex,
});
@@ -101,12 +101,32 @@ function enqueueRemove(parentID, fromIndex) {
parentNode: null,
type: ReactMultiChildUpdateTypes.REMOVE_NODE,
markupIndex: null,
- textContent: null,
+ content: null,
fromIndex: fromIndex,
toIndex: null,
});
}
+/**
+ * Enqueues setting the markup of a node.
+ *
+ * @param {string} parentID ID of the parent component.
+ * @param {string} markup Markup that renders into an element.
+ * @private
+ */
+function enqueueSetMarkup(parentID, markup) {
+ // NOTE: Null values reduce hidden classes.
+ updateQueue.push({
+ parentID: parentID,
+ parentNode: null,
+ type: ReactMultiChildUpdateTypes.SET_MARKUP,
+ markupIndex: null,
+ content: markup,
+ fromIndex: null,
+ toIndex: null,
+ });
+}
+
/**
* Enqueues setting the text content.
*
@@ -121,7 +141,7 @@ function enqueueTextContent(parentID, textContent) {
parentNode: null,
type: ReactMultiChildUpdateTypes.TEXT_CONTENT,
markupIndex: null,
- textContent: textContent,
+ content: textContent,
fromIndex: null,
toIndex: null,
});
@@ -237,6 +257,38 @@ var ReactMultiChild = {
}
},
+ /**
+ * Replaces any rendered children with a markup string.
+ *
+ * @param {string} nextMarkup String of markup.
+ * @internal
+ */
+ updateMarkup: function(nextMarkup) {
+ updateDepth++;
+ var errorThrown = true;
+ try {
+ var prevChildren = this._renderedChildren;
+ // Remove any rendered children.
+ ReactChildReconciler.unmountChildren(prevChildren);
+ for (var name in prevChildren) {
+ if (prevChildren.hasOwnProperty(name)) {
+ this._unmountChildByName(prevChildren[name], name);
+ }
+ }
+ this.setMarkup(nextMarkup);
+ errorThrown = false;
+ } finally {
+ updateDepth--;
+ if (!updateDepth) {
+ if (errorThrown) {
+ clearQueue();
+ } else {
+ processQueue();
+ }
+ }
+ }
+ },
+
/**
* Updates the rendered children with new children.
*
@@ -355,7 +407,7 @@ var ReactMultiChild = {
* @protected
*/
createChild: function(child, mountImage) {
- enqueueMarkup(this._rootNodeID, mountImage, child._mountIndex);
+ enqueueInsertMarkup(this._rootNodeID, mountImage, child._mountIndex);
},
/**
@@ -378,6 +430,16 @@ var ReactMultiChild = {
enqueueTextContent(this._rootNodeID, textContent);
},
+ /**
+ * Sets this markup string.
+ *
+ * @param {string} markup Markup to set.
+ * @protected
+ */
+ setMarkup: function(markup) {
+ enqueueSetMarkup(this._rootNodeID, markup);
+ },
+
/**
* Mounts a child with the supplied name.
*
diff --git a/src/renderers/shared/reconciler/ReactMultiChildUpdateTypes.js b/src/renderers/shared/reconciler/ReactMultiChildUpdateTypes.js
index d6ccf9907184..9cf1fb1f336b 100644
--- a/src/renderers/shared/reconciler/ReactMultiChildUpdateTypes.js
+++ b/src/renderers/shared/reconciler/ReactMultiChildUpdateTypes.js
@@ -25,6 +25,7 @@ var ReactMultiChildUpdateTypes = keyMirror({
INSERT_MARKUP: null,
MOVE_EXISTING: null,
REMOVE_NODE: null,
+ SET_MARKUP: null,
TEXT_CONTENT: null,
});
diff --git a/src/test/ReactDefaultPerfAnalysis.js b/src/test/ReactDefaultPerfAnalysis.js
index 869c2b0e39b5..3dee0db82174 100644
--- a/src/test/ReactDefaultPerfAnalysis.js
+++ b/src/test/ReactDefaultPerfAnalysis.js
@@ -20,11 +20,11 @@ var DOM_OPERATION_TYPES = {
INSERT_MARKUP: 'set innerHTML',
MOVE_EXISTING: 'move',
REMOVE_NODE: 'remove',
+ SET_MARKUP: 'set innerHTML',
TEXT_CONTENT: 'set textContent',
'updatePropertyByID': 'update attribute',
'deletePropertyByID': 'delete attribute',
'updateStylesByID': 'update styles',
- 'updateInnerHTMLByID': 'set innerHTML',
'dangerouslyReplaceNodeWithMarkupByID': 'replace',
};