I have a setup where log entries are written and stored in markdown plain text. Come time to render these in the browser, they're run through a markdown to HTML converter, and that gets rendered by the browser. In React, despite the fact that the code never works with HTML except at the very last step (edits occur on plain text, saving occurs on plain text, plain text in the props/state, etc) the only way to get this data loaded in is by using the cumbersome dangerouslySetInnerHTML attribute, even though this HTML is not human-generated in any way, and isn't "dynamic" in the traditional sense; it's just the snapshot HTML representation of my text, in the same way that the HTML that React generates is simply a snapshot representation of my component.
var MarkDown = React.createClass({
render: function() {
// completely transient data; It has no meaning outside of the render function
var html = markdown.toHTML(this.props.text);
return <div className="post" dangerouslySetInnerHTML={{__html: html }}</div>;
}
});
Is there something available that doesn't "lie" about what is being done in this scenario, like a way to tell React that there are additional safe processors available, such as:
React.registerContentTransformer("markdown", markdown.toHTML);
var MarkDown = React.createClass({
render: function() {
return <div className="post">{markdown: this.props.text}</div>;
}
});
(or whatever syntax would work that makes it explicit we're not working with human-generated dynamic HTML content, but with rigidly transformed, syntactically-correct-unless-the-transformer-has-bugs output)
edit: to further explain the motivation for this question/request, the dangerouslySetInnerHTML mechanism is great and we definitely need it for user-generated HTML strings. However, there should also be a way to extend the mechanism that exists inside the render function for transforming data that isn't HTML but can be snapshot-serialized to HTML (like React components themselves). An additional API for telling the JSX transformer that there are additional transformers available that can be used exclusively during render() using special curly bracket syntax would be close to ideal.
People would still need to use dangerouslySetInnerHTML for regular HTML string data, but people who have data that isn't HTML at all, merely transiently transformable into HTML form, should not need to say "this HTML is dangerous"; it's only as dangerous as the transformer makes it, putting it on part with the JSX transformer itself (bugs notwithstanding, the resulting HTML should always be well formed and legal)
if it's the syntax that troubles you, you can do :
function markdown(input){
return {
__html : markdown.toHTML(input)
}
}
and use
render() {
return <div dangerouslySetInnerHTML={markdown(this.props.text)} />
}
on a side note, I don't like the idea of react handling things like contentTransformer as react tries to dissuade you from using plain HTML.
That looks like a thing that isn't registered into React anywhere, which means it'd get duplicated in a million components. That's not a good solution, and the "API lie" is still right there (we're still saying it's dangerouslySetInnerHTML in the JSX, which it isn't, and we can guarantee that because we never work with HTML, and the transformation to HTML data is programmatically done, and has none of the danger that comes with user-content or on-the-fly-from-string HTML)
As for dissuading you from using plain HTML: JSX already isn't HTML (in fact, still thinking it is is one of the major pitfalls, where people keep trying to select/manipulate it as if it is). It's just a kind-of XML with React-specific behaviour in which any tag, even those that look like HTML tags, have pretty much nothing to do with real HTML. It just ends up generating an HTML snapshot all the way at the end when the browser needs to actually load up a DOM.
yes, that's what I said, my sentence wasn't very clear.
as for the duplication, you can use :
a module
var markdown = require("utils/markdown")
render() {
return <div dangerouslySetInnerHTML={markdown(this.props.text)} />
}
context
var context = {
md : require("utils/markdown")
}
React.withContext(context, function(){
React.render(<Component />, mountNode)
})
// with
React.createClass({
contextTypes : {
md : React.PropTypes.func
},
render() {
return <div dangerouslySetInnerHTML={this.context.md(this.props.text)} />
}
})
and/or a mixin
var MarkdownMixin = {
markdownHTML(string) {
return {
dangerouslySetInnerHTML : {
__html : markdown(string)
}
}
}
}
React.createClass({
mixins : [
MarkdownMixin
],
render() {
return <div {...this.markdownHTML(this.props.text)} />
}
})
ah! yeah the mixin is probably the thing I'm looking for then, thanks.
It's still dangerous unless your Markdown parser is somehow immune to XSS. (Don't count on it.)
IMO React does the right thing by forcing you to explicitly admit these parts are not secure.
There is, however, #2134.
Of course, but that's equally true for JSX itself, and is what we invented CSP for. The security of each custom transformer would necessarily rest with the authors of those transformers (any massively used markdown transformer like the marked or markdown packages could reasonably be expected to have the basics in place, with CSP adding the bits that they miss on a site-by-site basis)
You could just parse, and traverse the nodes and call createElement yourself, then strip anthing you feel is dangerous. That's all I do for react-htmlparser2.
I think you're using "just" incorrectly in that recommendation - it tells me to write my own parser, the output of which I definitely won't trust until it passes a comprehensive suite of tests, all of which requires I spend lots of time developing a new piece of software instead of "just" tying already existing software designed for this very purpose into React =)
Mixins work, but still feel a little hacky; a React.registerTransformer would be much more awesome, but not as required as I thought when I filed this issue.
Most helpful comment
It's still dangerous unless your Markdown parser is somehow immune to XSS. (Don't count on it.)
IMO React does the right thing by forcing you to explicitly admit these parts are not secure.
There is, however, #2134.