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.
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.
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} />
);
}
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)
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With