Injecting HTML into your React components with dangerouslySetInnerHTML isn’t always the answer…
React has a long and great history of being able to do web development pretty much any way you want. Whether its functional or class based components or in this case being able to allow for a dynamic block of HTML to be injected into your component.
As with anything in software engineering, there is always a balance.
In the following blog post, we will be working on the scenario where we have a block of content from a content management system [CMS] which could include some HTML.
React makes this really easy, dangerouslySetInnerHTML is a prop already available in the framework. Before we get into specifics, here is a quick example of what it looks like:
import React from "react";
const myContent = "Hello world!";
const MessageComponent = ({ content }) => {
return <p>{content}</p>;
};
export function App(props) {
return (
<div className="App">
<MessageComponent content={myContent} />
</div>
);
}
Simple enough, and the output looks like so:
![]()
Now, what if we change up the myContent variable to contain HTML content?
import React from "react";
const myContent = "<p>Hello <i>World</i><b>!</b></i></p>";
const MessageComponent = ({ content }) => {
return <p>{content}</p>;
};
export function App(props) {
return (
<div className="App">
<MessageComponent content={myContent} />
</div>
);
}
React doesn’t know about this by default, so will output it in its raw format:
![]()
This is where dangerouslySetInnerHTML comes into play. Lets add it and see what happens:
import React from "react";
const myContent = "<p>Hello <i>World</i><b>!</b></p>";
const MessageComponent = ({ content }) => {
return <p dangerouslySetInnerHTML={{ __html: content }} />;
};
export function App(props) {
return (
<div className="App">
<MessageComponent content={myContent} />
</div>
);
}
The output is as so, and is a success!
![]()
Well, it works at least..
Here, all we are doing is smashing the content into the DOM. React will do this by setting the following with setInnerHTML or the innerHTML on the object property. The innerHTML - as per the Mozilla Developer Network [MDN] , will apply the string of HTML passed to the property and render it on screen. The issue with this is you can pass anything via your CMS. Say the string was maliciously set in the CMS system by someone, as you want to be able to allow your users to pass in content with links? Other examples are Cross Site Scripting [XSS], but we will focus on this potential security issue in this article.
This means that anything passed to dangerouslySetInnerHTML is ignored from a responsibility aspect by React and pushes that ownership to the users client. This means zero sanitisation - eek.
A contrived example would be someone passing an element with a script underneath it, like this Read this <u onmouseover="alert('Hey')">text<u>. If this were instead a script that would copy the cookies and post them off somewhere to decode, sell or what ever then the user would not know this and you’ve caused a potential security hole. When an element is passed with something that can interact, it will be executed as intended.
What about those pesky script tags?
When you pass a script tag into the innerHTML property then you can rest assured knowing that the browser has your back for that with the HTML spec.
HTML specifies that a
<script>tag inserted withinnerHTMLshould not execute.
[reference: https://developer.mozilla.org/en-US/docs/Web/API/Element/innerHTML#security_considerations]
How to allow custom HTML in a react component
Enter a small package called html-react-parser, a light package that will go through the raw HTML that is passed and convert each “node” into a React element. This means that anything in the content block will be set as an HTML element - just like it does in dangerouslySetInnerHTML. The difference this time though is that the parser will ignore things like the onCick, or onmouseover properties and only allow style and custom props, like data-test or aria labels. Thus, meaning that your raw HTML is now sanitised.
This is as simple as:
import React from 'react';
import parse from 'html-react-parser';
const myContent = "<p>Hello <i>World</i><b>!</b></p>";
const MessageComponent = ({content}) => {
const sanitisedContent = parse(content);
return <p>{content}<p/>;
}
export function App(props) {
return (
<div className='App'>
<MessageComponent content={myContent}/>
</div>
);
}
Now you have more protection on bringing that content from your CMS into your app and it containing HTML markup without having to worry about malicious behaviour.
Side note…
Whilst it is possible to use dangerouslySetInnerHTML, it is always recommended not too - hence why it has “dangerously” in the property name.. Its not a problem to use if you control the input, but if that ever changes and you don’t remove the way the HTML markup is smashed in, then you could be in for a world of pain if someone uses it to their advantage. Adding this package will add less than 500kbs when unpacked, so its totally worth it for that piece of mind!