Markdown previewer: dangerouslySetInnerHTML doesn't update

The state updates, but the dangerouslySetInnerHTML doesn’t update. I can’t figure out why. Can someone give me a hint?


class MarkdownPreviewer extends React.Component{
   constructor(props){
     super(props);
     this.state = {
       input: "# some **preview** text",
       theMarked : marked('# some _**preview**_ text')
     };
     this.handleChange = this.handleChange.bind(this);
     
   }
  
  handleChange(event){
    console.log("raw event: " + event.target.value);
    console.log("theMarked before: " + this.state.theMarked);
    
    this.setState = {
      input: this.state.input = event.target.value,
      theMarked: this.state.theMarked = marked(this.state.input)
    }
    console.log("marked after: " + this.state.theMarked);
  }
  
  render(){
    
    return(
      <div id="container">
        <p>bobo</p>
        <textarea id="editor" onChange={this.handleChange}>
          {this.state.input}
        </textarea>
        <div id="preview" dangerouslySetInnerHTML={{__html: this.state.theMarked }}>
          
        </div>
      </div>
    );
    
      
    
  }
  
};

const insert = document.getElementById("main");


ReactDOM.render(<MarkdownPreviewer />, insert);

Thanks.

I’m not the expert, but I looked at my own markdown previewer and tried to find the differences.

I think the problem is either that this.state.theMarked is a variable which is only a reference to a value stored or that dictionaries are immutable. When you set __html to this.state.theMarked, you’re actually setting __html to the value at the end of the reference from this.state.theMarked.

Whichever cause it is, the result is that if this.state.theMarked == ‘fish’ at the time when the page is rendered, __html is set to ‘fish’. Later, you re-assign this.state.theMarked (you’re not really changing it, since variables are also immutable), but by the time someone is using the page, {__html: ‘fish’} now and not {__html: this.state.theMarked} at all, so there’s no reason to change __html.

Try defining function this.getMarked(), which returns the current value of this.state.theMarked. Then set {__html: this.getMarked()}. Now the value stored inside of __html is a formula that retrieves the value stored in a variable, and should change when you need.

This explanation may not be totally correct, but I believe the solution should work. Hopefully someone comes along to correct where I went off the rails eventually.

Thank you for the suggestion. I did write it as you suggested, and it still failed to update it. I suspected that what you said was correct, and it was hardcoding it in a sense. I can’t seem to find away around that.

Mark

Did you include this.getMarked = this.getMarked.bind(this); in the constructor after you added the getMarked function?

Yes. I’m able to change the state successfully, it’s not rendering though. Trying a new tactic, and I’m going to run the state through a child to see if I can force updating.

In my first suggestion, I was mistaken about one part. I suggested setting {__html: getMarkup()}, but I just rechecked and that’s not how it is in my own code


getMarkup() {
    var rawMarkup = marked(this.state.input.replace(/(\r)/g,"<br>"), {sanitize: true});
    return { __html: rawMarkup };

<div id='preview' class="in-out" dangerouslySetInnerHTML=     {this.getMarkup()}>

You may have tried it this way already and been too polite to point out my mistake, but if not I hope it helps.

I think that the way I originally set it sets the value of __html to actually BE a function, so that in theory you could call __html() and that would retrieve the value in this.state.theMarked (lol). Instead, this version sets the dangerouslySetInnerHTML attribute of the div to be a formula, and the formula returns the innerhtml object with a fixed value inside instead of a formula.

Also:

Not sure if this makes its own difference, but in my code the input looks like this:

 <textarea id="editor" class="in-out" onChange={this.handleChange} onKeyUp={this.handleChange} defaultValue={this.state.input}/> 

In your code {this.state.input} is set as a formula inside of the textarea, rather than set as the defaultValue attribute. I’m not exactly sure what complications that could cause, but it’s something I see that’s different so maybe it helps.

I really appreciate all the time you’ve put in. I’ve now used your code to see if I could get it to work that way, and still no avail. Below is the modified code. The function works the first pass through, but doesn’t update the div element with additions. It never hits the getMarkup() function after the first pass. I must be missing something fundamental, because I feel like it should be working, and isn’t.

class MarkdownPreviewer extends React.Component{
   constructor(props){
     super(props);
     this.state = {
       input: "# some **preview** text"
     };
     this.handleChange = this.handleChange.bind(this);
     this.getMarkup = this.getMarkup.bind(this);
     
   }
  
  handleChange(event){
      this.setState = {
      input: this.state.input = event.target.value 
    }
}
  getMarkup() {
    var rawMarkup = marked(this.state.input.replace(/(\r)/g,"<br>"), {sanitize: true});
    console.log(rawMarkup);
    return { __html: rawMarkup };

  }
  
  render(){
    
    return(
      <div id="container">
       
        <textarea id="editor" onChange={this.handleChange} defaultValue={this.state.input} onKeyUp={this.handleChange} />
         <div id='preview' dangerouslySetInnerHTML=     {this.getMarkup()}>  
        </div>        
      </div>
    );  
  }
  
};
const insert = document.getElementById("main");


ReactDOM.render(<MarkdownPreviewer />, insert);