'dangerouslySetInnerHtml doesn't update during render
So I made a component for including content-editable components in my app. I copied it from some gist I believe, then edited to what i needed.
The code is below. When I edit it, it triggers updates on the parent just fine, but when I attempt to set props.html in the parent, it doesn't reflect in the UI. FURTHER, the console.log shows that this.props.html is equal to '' a blank string, yet the UI doesn't update, and maintains the text that was originally in there.
I don't understand how this is possible... dangerouslySetInnerHtml = {__html: ''} should make it so the UI reflects an empty string... it feels like it should be impossible for it to show the old text.
var React = require('react');
var ContentEditable = React.createClass({
render: function(){
//TODO: find where html=undefined and fix it! So I can remove this? Maybe I should keep this safety.
var html = this.props.html || '';
console.log('content editable render, html: ', this.props.html);
return <div id="contenteditable"
onInput={this.emitChange}
onBlur={this.emitChange}
contentEditable
dangerouslySetInnerHTML={{__html: html}}></div>;
},
shouldComponentUpdate: function(nextProps){
return nextProps.html !== this.getDOMNode().innerHTML;
},
emitChange: function(){
var html = this.getDOMNode().innerHTML;
if (this.props.onChange && html !== this.lastHtml) {
this.props.onChange({
target: {
value: html
}
});
}
this.lastHtml = html;
}
});
module.exports = ContentEditable;
(A little background, I'm trying to clear my input after submitting it to be saved. The clearing isn't working, hence this question.)
Solution 1:[1]
I got a very similar problem using contentEditable
and shouldComponentUpdate
, it looks like there is a bug when resetting the innerHTML to the same previous value using dangerouslySetInnerHTML
function (or prop) (I think it does not work even if you insert the code without using it) ... I suspect (this is just an idea) that React compares the last value set through dangerouslySetInnerHTML
with the new one you are trying to send and decides not to update the component because it is the same (even if the real innerHtml has changed due to user interactions, because those interactions does not trigger any state or props update on React).
Solution: The easiest solution I found was to use a different key each time I needed it to re-render. for example using key={Date()}
.
Example: Here you can find your code (I changed some of it to make it work), when you type '?' into the div, the text inside the ContentEditable component should become an empty string (i.e. ''), it works only once, the second time you type '?' won't work because the innerHTML for react will be the same to the one you're setting (i.e. an empty string so it won't update the component).
And here, I added the key={Date()}
(this is the easiest way to show you that this work, but it is not the best way to set a unique key each time it re-render) to the editable component, now you can type any number of '?' and it will work.
Solution 2:[2]
I found another solution that is probably better than generating random keys. Putting a key specifically on the div that calls #dangerouslySetInnerHtml, and not just on the component itself
<div class='wrapper'>
<div key={this.props.thing.id} dangerouslySetInnerHtml={this.props.thing.content} />
</div>
Solution 3:[3]
My (very simple) React (version 16) app: It has a contentEditable
<div>
.
It successfully re-renders this <div>
upon a progression of submit button clicks. Instead of dangerouslySetInnerHtml
, I used ref={el => this.myRefElem = el}
with componentWillUpdate(nextProps) { this.myRefElem.innerHTML = nextProps.myInputText; }
For me, nextProps
was important for the proper value to re-render. See my app's project files, to see the rest of the required code.
CLICK-HERE? to see my React app. It has a button to download its (development mode) project files. It (basically) only has an index.js file. - - - This app was initiated by mlbrgl, who asked me for an alternative technique.
Solution 4:[4]
I ran into the same issue (React 16) and used an approach suggested by MLR which consists in dropping dangerouslySetInnerHTML
and using componentDidMount()
instead for the initial render and componentDidUpdate()
for any subsequent renders.
Solution here, adapted to React 16: https://codepen.io/mlbrgl/pen/PQdLgb
These hooks would perform the same update, directly updating innerHTML
from props:
componentDidMount() {
this.updateInnerHTMLFromProps();
}
componentDidUpdate() {
this.updateInnerHTMLFromProps();
}
updateInnerHTMLFromProps() {
this.refEl.innerHTML = this.props.html;
}
This makes it clearer (for me at least) to see what is really going on, without having the false expectation that dangerouslySetInnerHTML
would keep the DOM in sync in all circumstances, as suggested by Mike Woodcock here https://stackoverflow.com/a/38548616/9408759.
For a complete view of the problem and both solutions outlined here, please check https://codepen.io/mlbrgl/pen/QQVMRP.
Solution 5:[5]
This is not the case here but make sure you always dangerouslyset html on div tag and never on span, p ... because if span element child is div, there will be a problem.
Solved rerender bug to me
Solution 6:[6]
Adding key
property for an element with dangerouslySetInnerHTML
did resolve my issue.
As a key I used
key={new Date().getTime()} // timestamp
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | Mike W |
Solution 2 | Brent L |
Solution 3 | |
Solution 4 | |
Solution 5 | |
Solution 6 | Ismoil Shokirov |