I'm displaying text that was stored in the database. The data is coming from firebase as a string (with newline breaks included). To make it display as HTML, I originally did the following:
<p className="term-definition"
dangerouslySetInnerHTML={{__html: (definition.definition) ? definition.definition.replace(/(?:\r\n|\r|\n)/g, '<br />') : ''}}></p>
This worked great. However there's one additional feature. Users can type [word]
and that word will become linked. In order to accomplish this, I created the following function:
parseDefinitionText(text){
text = text.replace(/(?:\r\n|\r|\n)/g, '<br />');
text = text.replace(/\[([A-Za-z0-9'\-\s]+)\]/, function(match, word){
// Convert it to a permalink
return (<Link to={'/terms/' + this.permalink(word) + '/1'}>{word}</Link>);
}.bind(this));
return text;
},
I left out the this.permalink
method as it's not relevant. As you can see, I'm attempting to return a <Link>
component that was imported from react-router.However since it's raw HTML, dangerouslySetInnerHTML
no longer works properly.
So I'm kind of stuck at this point. What can I do to both format the inner text and also create a link?
You could split the text into an array of Links + strings like so:
import {Link} from 'react-router';
const paragraphWithLinks = ({markdown}) => {
const linkRegex = /\[([\w\s-']+)\]/g;
const children = _.chain(
markdown.split(linkRegex) // get the text between links
).zip(
markdown.match(linkRegex).map( // get the links
word => <Link to={`/terms/${permalink(word)}/1`}>{word}</Link> // and convert them
)
).flatten().thru( // merge them
v => v.slice(0, -1) // remove the last element (undefined b/c arrays are different sizes)
).value();
return <p className='term-definition'>{children}</p>;
};
The best thing about this approach is removing the need to use dangerouslySetInnerHTML
. Using it is generally an extremely bad idea as you're potentially creating an XSS vulnerability. That may enable hackers to, for example, steal login credentials from your users.
In most cases you do not need to use dangerouslySetHTML
. The obvious exception is for integration w/ a 3rd party library, which should still be considered carefully.
I ran into a similar situation, however the accepted solution wasn't a viable option for me.
I got this working with react-dom
in a fairly crude way. I set the component up to listen for click events and if the click had the class of react-router-link
. When this happened, if the item has a data-url
property set it uses browserHistory.push
. I'm currently using an isomorphic app, and these click events don't make sense for the server generation, so I only set these events conditionally.
Here's the code I used:
import React from 'react';
import _ from 'lodash';
import { browserHistory } from 'react-router'
export default class PostBody extends React.Component {
componentDidMount() {
if(! global.__SERVER__) {
this.listener = this.handleClick.bind(this);
window.addEventListener('click', this.listener);
}
}
componentDidUnmount() {
if(! global.__SERVER__) {
window.removeEventListener("scroll", this.listener);
}
}
handleClick(e) {
if(_.includes(e.target.classList, "react-router-link")) {
window.removeEventListener("click", this.listener);
browserHistory.push(e.target.getAttribute("data-url"));
}
}
render() {
function createMarkup(html) { return {__html: html}; };
return (
<div className="col-xs-10 col-xs-offset-1 col-md-6 col-md-offset-3 col-lg-8 col-lg-offset-2 post-body">
<div dangerouslySetInnerHTML={createMarkup(this.props.postBody)} />
</div>
);
}
}
Hope this helps out!
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.