Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Call React Component Function from onclick in dangerouslySetInnerHTML

React newbie here. I have a contenteditable div which has dangerouslySetInnerHTML as the child, since I need to format whatever the user enters,at runtime. On a particular span click inside the HTML, I want to setState one of the variables of the containing component.

Can this be done?

If not, how should I change my structure ?

Here's the code:

updateText:function(){

    var txt = $('#text_Box').text();

    if(txt.indexOf('@Name') > -1)
    {
        txt = txt.replace('@Name','<span class=\'tagged\' contenteditable = \'false\' onclick=\'doSomething()\'>:Name</span>');

    }
    this.setState({userText:txt});
}, 
render:function(){
  return <div className="inputDiv" contentEditable="true" type="text" className="form-control" id="text_Box" dangerouslySetInnerHTML={{__html:this.state.userText}} onInput = {this.updateText} />

}

the doSomething() method is what I'm taking about.

like image 553
user4773422 Avatar asked May 29 '15 07:05

user4773422


3 Answers

If you want your spans to respond to click events, you should assign event handler(doSomething) only after your component is rerendered, because when you assing new value to innerHtml, all event handlers within this component are cleaned. Another solution is using event delegation like this:

onClick: function(e) {
    var $el = $(e.target);
    if ($el.is('span.tagged')) {
        this.doSomething($el);
    }
},

render:function(){
    return (
        <div 
            className="inputDiv form-control" 
            contentEditable="true"
            onClick={this.onClick}
            type="text" 
            id="text_Box" 
            dangerouslySetInnerHTML={{__html: this.state.userText}} 
            onInput={this.updateText} />
    );
}

Another possible solution is to work with DOM tree directly using createElement, createTextNode and appendChild methods.

like image 80
beshanoe Avatar answered Nov 14 '22 22:11

beshanoe


Try this:

updateText: function() {
    var txt = $('#text_Box').text();

    if (txt.indexOf('@Name') > -1) {
        txt = txt.replace('@Name', '<span class="tagged" contenteditable="false" onclick="' + this.doSomething() + '">:Name</span>');
    }
    this.setState({userText: txt});
}, 

render:function(){
    return (
        <div 
            className="inputDiv form-control" 
            contentEditable="true" 
            type="text" 
            id="text_Box" 
            dangerouslySetInnerHTML={{__html: this.state.userText}} 
            onInput={this.updateText} />
    );
}
like image 36
marcel Avatar answered Nov 14 '22 22:11

marcel


I had a similar requirement recently. The react app was being given a block of html with href attributes that needed to be converted into onClick events so that we could resolve and route the links within the react app.

My solution was to use regex and then register my own event listeners

//given a block of html as a string
myHtml = '<div href="/goSomewhere"></div>'

//match all href attributes
let reg: RegExp = /href=".*?"/g

//replace the href attribute with a custom "resolve" attribute
myHtml.replace(reg, (href: string) => {
  //then match any characters between the quotation marks in the href attribute to extract the uri itself
  let uri = href.match(/(?<=href=").*?(?=")/g)
  return `resolve="${uri}"`
})


//Render the html
render() { 
   return <div dangerouslySetInnerHTML = {{__html: myHtml}} />
}

//helper function to return an array containing all nodes with a "resolve" attribute
getElementByAttribute(attr: string) {
  let nodeList = document.getElementsByTagName('*')
  let nodeArray = []
  for (let i = 0; i < nodeList.length; i++) {
    if (nodeList[i].getAttribute(attr)) nodeArray.push(nodeList[i])
  }
  return nodeArray
}

//once rendered find the tag that require resolving 
componentDidUpdate() {
  let nodes = this.getElementByAttribute('resolve')
  for (let i = 0; i < nodes.length; i++) {
    //create pointer outside of the onclick event allowing closure
    let href = nodes[i].getAttribute('resolve')

    nodes[i].addEventListener('click', (e) => {
      if (href) this.linkResolver(href);
    })

    //remove the custom attribute to cleanup
    nodes[i].removeAttribute('resolve')
  }
}

//linkResolver is a function within react
//you now have an onclick event triggered from dangerouslySetInnerHTML
linkResolver(href: string) {
  console.log(href)
}
like image 45
user3203348 Avatar answered Nov 14 '22 21:11

user3203348