6

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.

5
  • pls show us your code you have so far Commented May 29, 2015 at 7:41
  • @marcel, have added the code. Commented May 29, 2015 at 8:19
  • what should onInput do? does it work? does it call the method as expected? Commented May 29, 2015 at 8:49
  • onInput calls the designated function whenever user enters text in the div. It works fine. Commented May 29, 2015 at 10:22
  • ok. i didn't know this attribute. :-) Commented May 29, 2015 at 10:30

3 Answers 3

9

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.

Sign up to request clarification or add additional context in comments.

4 Comments

the dangerous HTML that is going to be displayed is dynamic and might change depending on options selected by the user previously. In such a case, there might not be any <span> in it at all, but the same function will be called on click of whatever DOM is present
Yes, in case of having no span elements there will be event handler on div, but I don't understand how the marcel's answer helps solving the problem, because it just invokes this.doSomething method inside updateText method, and span's onclick field receives the result of doSomething invocation. Anyway you can't assing span's click handler which will have direct access to this.doSomething this way
I see your point. Maybe I need to redo this entire thing. Thanks for pointing this out !
@beshanoe - your answer is the correct one. As you point out correctly in the accepted solution (and I don't get why it is accepted rather than yours) this.doSomething is going to get executed immediately and its result will be concatenated with the rest of the string. There is no way that approach will allow you to install a handler to be invoked in onClick...
3

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} />
    );
}

Comments

1

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)
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.