Skip to content

Commit 16483e3

Browse files
committed
Merge pull request #4427 from spicyj/gh-1232
Fix switching between dangerouslySetInnerHTML and children
2 parents 3ca5c15 + caae627 commit 16483e3

File tree

8 files changed

+115
-29
lines changed

8 files changed

+115
-29
lines changed

src/renderers/dom/client/ReactDOMIDOperations.js

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ var ReactMount = require('ReactMount');
1919
var ReactPerf = require('ReactPerf');
2020

2121
var invariant = require('invariant');
22-
var setInnerHTML = require('setInnerHTML');
2322

2423
/**
2524
* Errors for properties that should not be updated with `updatePropertyByID()`.
@@ -115,18 +114,6 @@ var ReactDOMIDOperations = {
115114
CSSPropertyOperations.setValueForStyles(node, styles);
116115
},
117116

118-
/**
119-
* Updates a DOM node's innerHTML.
120-
*
121-
* @param {string} id ID of the node to update.
122-
* @param {string} html An HTML string.
123-
* @internal
124-
*/
125-
updateInnerHTMLByID: function(id, html) {
126-
var node = ReactMount.getNode(id);
127-
setInnerHTML(node, html);
128-
},
129-
130117
/**
131118
* Updates a DOM node's text content set by `props.content`.
132119
*
@@ -171,7 +158,6 @@ ReactPerf.measureMethods(ReactDOMIDOperations, 'ReactDOMIDOperations', {
171158
updatePropertyByID: 'updatePropertyByID',
172159
deletePropertyByID: 'deletePropertyByID',
173160
updateStylesByID: 'updateStylesByID',
174-
updateInnerHTMLByID: 'updateInnerHTMLByID',
175161
updateTextContentByID: 'updateTextContentByID',
176162
dangerouslyReplaceNodeWithMarkupByID: 'dangerouslyReplaceNodeWithMarkupByID',
177163
dangerouslyProcessChildrenUpdates: 'dangerouslyProcessChildrenUpdates',

src/renderers/dom/client/__tests__/ReactDOMIDOperations-test.js

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ describe('ReactDOMIDOperations', function() {
1515
var DOMPropertyOperations = require('DOMPropertyOperations');
1616
var ReactDOMIDOperations = require('ReactDOMIDOperations');
1717
var ReactMount = require('ReactMount');
18+
var ReactMultiChildUpdateTypes = require('ReactMultiChildUpdateTypes');
1819
var keyOf = require('keyOf');
1920

2021
it('should disallow updating special properties', function() {
@@ -44,9 +45,17 @@ describe('ReactDOMIDOperations', function() {
4445

4546
var html = '\n \t <span> \n testContent \t </span> \n \t';
4647

47-
ReactDOMIDOperations.updateInnerHTMLByID(
48-
'testID',
49-
html
48+
ReactDOMIDOperations.dangerouslyProcessChildrenUpdates(
49+
[{
50+
parentID: 'testID',
51+
parentNode: null,
52+
type: ReactMultiChildUpdateTypes.SET_MARKUP,
53+
markupIndex: null,
54+
content: html,
55+
fromIndex: null,
56+
toIndex: null,
57+
}],
58+
[]
5059
);
5160

5261
expect(

src/renderers/dom/client/utils/DOMChildrenOperations.js

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
var Danger = require('Danger');
1616
var ReactMultiChildUpdateTypes = require('ReactMultiChildUpdateTypes');
1717

18+
var setInnerHTML = require('setInnerHTML');
1819
var setTextContent = require('setTextContent');
1920
var invariant = require('invariant');
2021

@@ -123,10 +124,16 @@ var DOMChildrenOperations = {
123124
update.toIndex
124125
);
125126
break;
127+
case ReactMultiChildUpdateTypes.SET_MARKUP:
128+
setInnerHTML(
129+
update.parentNode,
130+
update.content
131+
);
132+
break;
126133
case ReactMultiChildUpdateTypes.TEXT_CONTENT:
127134
setTextContent(
128135
update.parentNode,
129-
update.textContent
136+
update.content
130137
);
131138
break;
132139
case ReactMultiChildUpdateTypes.REMOVE_NODE:

src/renderers/dom/shared/ReactDOMComponent.js

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -884,10 +884,7 @@ ReactDOMComponent.Mixin = {
884884
}
885885
} else if (nextHtml != null) {
886886
if (lastHtml !== nextHtml) {
887-
BackendIDOperations.updateInnerHTMLByID(
888-
this._rootNodeID,
889-
nextHtml
890-
);
887+
this.updateMarkup('' + nextHtml);
891888
}
892889
} else if (nextChildren != null) {
893890
this.updateChildren(nextChildren, transaction, context);

src/renderers/dom/shared/__tests__/ReactDOMComponent-test.js

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -309,6 +309,30 @@ describe('ReactDOMComponent', function() {
309309
expect(container.firstChild.innerHTML).toEqual('adieu');
310310
});
311311

312+
it('should transition from innerHTML to children in nested el', function() {
313+
var container = document.createElement('div');
314+
React.render(
315+
<div><div dangerouslySetInnerHTML={{__html: 'bonjour'}} /></div>,
316+
container
317+
);
318+
319+
expect(container.textContent).toEqual('bonjour');
320+
React.render(<div><div><span>adieu</span></div></div>, container);
321+
expect(container.textContent).toEqual('adieu');
322+
});
323+
324+
it('should transition from children to innerHTML in nested el', function() {
325+
var container = document.createElement('div');
326+
React.render(<div><div><span>adieu</span></div></div>, container);
327+
328+
expect(container.textContent).toEqual('adieu');
329+
React.render(
330+
<div><div dangerouslySetInnerHTML={{__html: 'bonjour'}} /></div>,
331+
container
332+
);
333+
expect(container.textContent).toEqual('bonjour');
334+
});
335+
312336
it('should not incur unnecessary DOM mutations', function() {
313337
var container = document.createElement('div');
314338
React.render(<div value="" />, container);

src/renderers/shared/reconciler/ReactMultiChild.js

Lines changed: 68 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -53,14 +53,14 @@ var markupQueue = [];
5353
* @param {number} toIndex Destination index.
5454
* @private
5555
*/
56-
function enqueueMarkup(parentID, markup, toIndex) {
56+
function enqueueInsertMarkup(parentID, markup, toIndex) {
5757
// NOTE: Null values reduce hidden classes.
5858
updateQueue.push({
5959
parentID: parentID,
6060
parentNode: null,
6161
type: ReactMultiChildUpdateTypes.INSERT_MARKUP,
6262
markupIndex: markupQueue.push(markup) - 1,
63-
textContent: null,
63+
content: null,
6464
fromIndex: null,
6565
toIndex: toIndex,
6666
});
@@ -81,7 +81,7 @@ function enqueueMove(parentID, fromIndex, toIndex) {
8181
parentNode: null,
8282
type: ReactMultiChildUpdateTypes.MOVE_EXISTING,
8383
markupIndex: null,
84-
textContent: null,
84+
content: null,
8585
fromIndex: fromIndex,
8686
toIndex: toIndex,
8787
});
@@ -101,12 +101,32 @@ function enqueueRemove(parentID, fromIndex) {
101101
parentNode: null,
102102
type: ReactMultiChildUpdateTypes.REMOVE_NODE,
103103
markupIndex: null,
104-
textContent: null,
104+
content: null,
105105
fromIndex: fromIndex,
106106
toIndex: null,
107107
});
108108
}
109109

110+
/**
111+
* Enqueues setting the markup of a node.
112+
*
113+
* @param {string} parentID ID of the parent component.
114+
* @param {string} markup Markup that renders into an element.
115+
* @private
116+
*/
117+
function enqueueSetMarkup(parentID, markup) {
118+
// NOTE: Null values reduce hidden classes.
119+
updateQueue.push({
120+
parentID: parentID,
121+
parentNode: null,
122+
type: ReactMultiChildUpdateTypes.SET_MARKUP,
123+
markupIndex: null,
124+
content: markup,
125+
fromIndex: null,
126+
toIndex: null,
127+
});
128+
}
129+
110130
/**
111131
* Enqueues setting the text content.
112132
*
@@ -121,7 +141,7 @@ function enqueueTextContent(parentID, textContent) {
121141
parentNode: null,
122142
type: ReactMultiChildUpdateTypes.TEXT_CONTENT,
123143
markupIndex: null,
124-
textContent: textContent,
144+
content: textContent,
125145
fromIndex: null,
126146
toIndex: null,
127147
});
@@ -237,6 +257,38 @@ var ReactMultiChild = {
237257
}
238258
},
239259

260+
/**
261+
* Replaces any rendered children with a markup string.
262+
*
263+
* @param {string} nextMarkup String of markup.
264+
* @internal
265+
*/
266+
updateMarkup: function(nextMarkup) {
267+
updateDepth++;
268+
var errorThrown = true;
269+
try {
270+
var prevChildren = this._renderedChildren;
271+
// Remove any rendered children.
272+
ReactChildReconciler.unmountChildren(prevChildren);
273+
for (var name in prevChildren) {
274+
if (prevChildren.hasOwnProperty(name)) {
275+
this._unmountChildByName(prevChildren[name], name);
276+
}
277+
}
278+
this.setMarkup(nextMarkup);
279+
errorThrown = false;
280+
} finally {
281+
updateDepth--;
282+
if (!updateDepth) {
283+
if (errorThrown) {
284+
clearQueue();
285+
} else {
286+
processQueue();
287+
}
288+
}
289+
}
290+
},
291+
240292
/**
241293
* Updates the rendered children with new children.
242294
*
@@ -355,7 +407,7 @@ var ReactMultiChild = {
355407
* @protected
356408
*/
357409
createChild: function(child, mountImage) {
358-
enqueueMarkup(this._rootNodeID, mountImage, child._mountIndex);
410+
enqueueInsertMarkup(this._rootNodeID, mountImage, child._mountIndex);
359411
},
360412

361413
/**
@@ -378,6 +430,16 @@ var ReactMultiChild = {
378430
enqueueTextContent(this._rootNodeID, textContent);
379431
},
380432

433+
/**
434+
* Sets this markup string.
435+
*
436+
* @param {string} markup Markup to set.
437+
* @protected
438+
*/
439+
setMarkup: function(markup) {
440+
enqueueSetMarkup(this._rootNodeID, markup);
441+
},
442+
381443
/**
382444
* Mounts a child with the supplied name.
383445
*

src/renderers/shared/reconciler/ReactMultiChildUpdateTypes.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ var ReactMultiChildUpdateTypes = keyMirror({
2525
INSERT_MARKUP: null,
2626
MOVE_EXISTING: null,
2727
REMOVE_NODE: null,
28+
SET_MARKUP: null,
2829
TEXT_CONTENT: null,
2930
});
3031

src/test/ReactDefaultPerfAnalysis.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,11 @@ var DOM_OPERATION_TYPES = {
2020
INSERT_MARKUP: 'set innerHTML',
2121
MOVE_EXISTING: 'move',
2222
REMOVE_NODE: 'remove',
23+
SET_MARKUP: 'set innerHTML',
2324
TEXT_CONTENT: 'set textContent',
2425
'updatePropertyByID': 'update attribute',
2526
'deletePropertyByID': 'delete attribute',
2627
'updateStylesByID': 'update styles',
27-
'updateInnerHTMLByID': 'set innerHTML',
2828
'dangerouslyReplaceNodeWithMarkupByID': 'replace',
2929
};
3030

0 commit comments

Comments
 (0)