Editor’s note: This article was last updated on 12 December 2023 to discuss security risks associated with using dangerouslySetInnerHTML
, such as XSS attacks.
This article covers the reasoning behind using the dangerouslySetInnerHTML
property in a React application, which is the equivalent of the innerHTML
attribute in the browser DOM.
dangerouslySetInnerHTML
?dangerouslySetInnerHTML
is a property that you can use on HTML elements in a React application to programmatically set their content. Instead of using a selector to grab the HTML element and then setting its innerHTML
, you can use this property directly on the element.
When dangerouslySetInnerHTML
is used, React also knows that the contents of that specific element are dynamic, and, for the children of that node, it simply skips the comparison against the virtual DOM to gain some extra performance.
As the name of the property suggests, it can be dangerous to use dangerouslySetInnerHTML
because it makes your code vulnerable to cross-site scripting (XSS) attacks. This becomes an issue, especially if you are fetching data from a third-party source or rendering content submitted by users.
dangerouslySetInnerHTML
A use case where you need to set the HTML content of a DOM element is when you populate a <div>
element with the data coming from a rich text editor. Imagine you have a webpage where people can submit comments and you allow them to use a rich text editor. In this case, the output of that rich text editor is likely to be HTML with tags such as <p>
, <b>
, and <img>
.
Consider the following code snippet, which would render the string without being aware of the <b>
tag in it — meaning that the output would be just the string itself without any bold text, like lorem <b>ipsum</b>
:
const App = () => { const data = 'lorem <b>ipsum</b>'; return ( <div> {data} </div> ); } export default App;
But when dangerouslySetInnerHTML
is used, React becomes aware of the HTML tags and renders them properly. This time, the output would be rendered correctly with bold text (i.e., lorem ipsum):
const App = () => { const data = 'lorem <b>ipsum</b>'; return ( <div dangerouslySetInnerHTML={{__html: data}} /> ); } export default App;
Note that it should be an object with the __html
key passed to dangerouslySetInnerHTML
. Other than that, the element on which you use the dangerouslySetInnerHTML
property shouldn’t have any children, hence the use of <div>
element as a self-closing tag.
dangerouslySetInnerHTML
Using dangerouslySetInnerHTML
means you’re making use of an executable code, and this is quite dangerous. This is why React had to make passing objects to dangerouslySetInnerHTML
a requirement as a safeguard to prevent developers from using it without going through the documentation and becoming aware of the potential danger.
Using dangerouslySetInnerHTML
makes your site vulnerable to cross-site scripting (XSS) attacks, which can cause damage to your site and its users. XSS attacks can have various forms and can lead to issues like unauthorized access, session hijacking, and the theft of sensitive information.
These attacks can happen when you fetch HTML from an untrusted third-party source or when rendering content submitted by users. These two forms of attacks are known as DOM based XSS and persistent/stored XSS, respectively.
DOM based attacks happen from the client and, like the example we gave, they can be caused by the developer fetching/copying HTML data from an untrusted third-party source, and rendering this data through the dangerouslySetInnerHTML
prop.
Persistent/stored attacks happen when a user injects a script through any forms available on your website. For example, it could be a comment section in which you provide a rich text editor as the input field — like the example we saw earlier.
This comment (containing a malicious script) becomes stored in your server, and whenever other users view the comment, the script executes in their browser.
Consider the following examples where a JavaScript event is attached to an HTML element. Although these are harmless examples, they are proof of concepts that show how an HTML element can be exploited to run malicious scripts:
const App = () => { const data = `lorem <b onmouseover="alert('mouseover');">ipsum</b>`; return ( <div dangerouslySetInnerHTML={{__html: data}} /> ); } export default App; const App = () => { const data = `lorem ipsum <img src="" onerror="alert('message');" />`; return ( <div dangerouslySetInnerHTML={{__html: data}} /> ); } export default App;
This is why it is recommended to only use dangerouslySetInnerHTML
when necessary, and when it isn’t necessary for the HTML code to be executable, you could render the HTML data as it is.
When necessary, there is a solution to prevent these dangerous attacks, and that is through sanitizing whatever HTML data you intend to pass into dangerouslySetInnerHTML
.
dangerouslySetInnerHTML
Sanitizing your HTML code detects potentially malicious parts in HTML code and then outputs a clean and safe version of it. The most popular sanitizer for HTML is DOMPurify.
Let’s use its online demo to sanitize the above-mentioned HTML codes and see how it detects and filters out parts of the code that are likely to be dangerous when executed:
Original lorem <b onmouseover="alert('mouseover');">ipsum</b> Sanitized lorem <b>ipsum</b> Original lorem ipsum <img src="" onerror="alert('message');" /> Sanitized lorem ipsum <img src="">
It’s good practice to use a sanitizer even when we trust the source of the data. With the DOMPurify package used, one of the examples above would be as follows:
import DOMPurify from 'dompurify' const App = () => { const data = `lorem <b onmouseover="alert('mouseover');">ipsum</b>` const sanitizedData = () => ({ __html: DOMPurify.sanitize(data) }) return ( <div dangerouslySetInnerHTML={sanitizedData()} /> ); } export default App;
The sanitizedData
function returns an object with the __html
key, which has a value returned from the DOMPurify.sanitize
function.
As expected, when we hover over the bold text, there is no alert function executed. Note that because DOMPurify needs a DOM tree and the Node environment does not have one, you either have to use the jsdom package to create a window
object and initialize DOMPurify with it, or use the isomorphic-dompurify package alone instead, which encapsulates both the DOMPurify and jsdom packages.
If you prefer the first option, you can refer to the following snippet from the documentation of DOMPurify:
const createDOMPurify = require('dompurify'); const { JSDOM } = require('jsdom'); const window = new JSDOM('').window; const DOMPurify = createDOMPurify(window); const clean = DOMPurify.sanitize(dirty);
dangerouslySetInnerHTML
Instead of using dangerouslySetInnerHTML
, you could make use of a library to render your executable HTML code. A good example is react-html-parser:
import DOMPurify from "dompurify"; import ReactHtmlParser from "react-html-parser"; const App = () => { const data = `lorem <b onmouseover="alert('mouseover');">ipsum</b>`; const sanitizedData = DOMPurify.sanitize(data); return <div>{ReactHtmlParser(sanitizedData)}</div>; }; export default App;
You’re still going to have to sanitize the data because the library doesn’t do that for you. What the library does is help you convert HTML strings into React components, which means it converts elements and attributes of an HTML string into their React equivalent.
dangerouslySetInnerHTML
is nothing but a replacement of innerHTML
in React and should be used with care. Although the name suggests danger in its use, taking the necessary measures by using a well-developed sanitizer ensures the code is clean and does not run unexpected scripts when rendered within the React node.
By using libraries like react-html-parser, you can avoid using dangerouslySetInnerHTML
entirely and also have the benefits of parsing your HTML strings as React components.
Install LogRocket via npm or script tag. LogRocket.init()
must be called client-side, not
server-side
$ npm i --save logrocket // Code: import LogRocket from 'logrocket'; LogRocket.init('app/id');
// Add to your HTML: <script src="https://cdn.lr-ingest.com/LogRocket.min.js"></script> <script>window.LogRocket && window.LogRocket.init('app/id');</script>
In this article, we’ll explore CSS cascade layers — and, specifically, the revert-layer
keyword — to help you refine your styling strategy.
Nushell is a modern, performant, extensible shell built with Rust. Explore its pros, cons, and how to install and get started with it.
The Zed code editor sets itself apart with its lightning-fast performance and cutting-edge collaborative features.
Infinite scrolling in Next.js no longer requires external libraries — Server Actions let us fetch initial data directly on the server.
One Reply to "Using dangerouslySetInnerHTML in a React application"
great post