dangerouslySetInnerHTML
in a React applicationEditor’s note: This article was last updated by Ikeh Akinyemi on 13 September 2024 to discuss how the release of React v18 impacts the use of dangerouslySetInnerHTML
, especially in the context of React Server Components and concurrent rendering. It also now covers SEO considerations when using dangerouslySetInnerHTML
.
dangerouslySetInnerHTML
?The dangerouslySetInnerHTML
property in a React application is the equivalent of the innerHTML
attribute in the browser DOM. In vanilla JavasScript, innerHTML
is a property of DOM elements that allows you to get or set the HTML content inside an element. It’s a part of the standard DOM API, not specific to React.
dangerouslySetInnerHTML
is React’s replacement for using innerHTML
. 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 can become an especially big issue if you are fetching data from a third-party source or rendering content submitted by users.
dangerouslySetInnerHTML
When you need to populate a <div>
element with data coming from a rich text editor.
Imagine you have a webpage where people can submit comments using 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
. Additionally, the element on which you use the dangerouslySetInnerHTML
property shouldn’t have any children, hence the use of the <div>
element as a self-closing tag.
Because the rendered data are valid HTML, you can add inline styles in the HTML content and it will be preserved. You can also use global CSS styles that target elements within the rendered HTML. This support also extends to inline HTML events like onclick
to the rendered content.
dangerouslySetInnerHTML
security risksUsing dangerouslySetInnerHTML
means you’re executing potentially unsafe code, which can be 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.
Examples of cross-site scripting attacks:
dangerouslySetInnerHTML
propConsider 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
.
Note that dangerouslySetInnerHTML
is simply a replacement for directly using innerHTML
. It was designed to insert raw HTML strings. As a result, it cannot be used to render React components.
dangerouslySetInnerHTML
Sanitizing your HTML code detects potentially malicious parts in HTML code and then outputs a clean and safe version of it. It’s good practice to use a sanitizer even when you trust the source of the data.
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="">
Here’s an example of how to use DOMPurify to sanitize HTML before using dangerouslySetInnerHTML
:
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. This ensures that any potentially malicious scripts are removed before rendering.
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 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
With the release of React 18 and subsequent versions, there have been significant changes in how React handles rendering and component lifecycle. These changes have implications for the use of dangerouslySetInnerHTML
, particularly in the context of Server Components and concurrent rendering.
dangerouslySetInnerHTML
React Server Components, introduced in React 18, allow components to be rendered on the server, reducing the amount of JavaScript sent to the client and thereby improving performance.
The security considerations for dangerouslySetInnerHTML
remain crucial in Server Components. When using dangerouslySetInnerHTML
with Server Components, there are a few important considerations to keep in mind:
dangerouslySetInnerHTML
. Server-side rendering (SSR) with dangerouslySetInnerHTML
can still expose you to XSS attacks if the content isn’t properly sanitizeddangerouslySetInnerHTML
can complicate this process, potentially leading to hydration errors if the content changes between server and client rendersdangerouslySetInnerHTML
can negate some of these benefits by increasing the size of the initial HTML payloaddangerouslySetInnerHTML
React 18 also introduced concurrent rendering, which allows React to prepare multiple versions of the UI at the same time. This has implications for how dangerouslySetInnerHTML
is handled.
React may render a component multiple times before committing the result to the DOM, and when using dangerouslySetInnerHTML
, it’s important to ensure that the content remains consistent across these potential re-renders.
Concurrent rendering aims to improve application responsiveness by allowing React to interrupt and prioritize certain renders. However, dangerouslySetInnerHTML
creates a “black box” that React can’t optimize, potentially impacting the benefits of concurrent rendering. And because concurrent rendering can lead to tearing (i.e., an inconsistent UI state) if not handled properly, extra care must be taken when using dangerouslySetInnerHTML
to ensure that the rendered content doesn’t depend on any external state that might change during rendering.
To mitigate these issues, consider using the useMemo
Hook to memoize the sanitized content:
import React, { useMemo } from 'react'; import DOMPurify from 'dompurify'; function SafeHTML({ content }) { const sanitizedContent = useMemo(() => ({ __html: DOMPurify.sanitize(content) }), [content]); return <div dangerouslySetInnerHTML={sanitizedContent} />; }
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 still need 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
Apart from the existing security and performance considerations, using dangerouslySetInnerHTML
can also have significant implications for SEO:
dangerouslySetInnerHTML
. However, if the content is loaded asynchronously or requires JavaScript execution, it may not be immediately visible to crawlersdangerouslySetInnerHTML
can impact page load times, especially if large chunks of HTML are being insertedOne of the approaches you can adopt to resolve some of the SEO issues mentioned above is to use Server Components to ensure that content inserted via dangerouslySetInnerHTML
is present in the initial HTML response. This makes it immediately visible to search engine crawlers.
Another approach when using dangerouslySetInnerHTML
is to maintain a semantic HTML structure inside of the rendered content. Use appropriate HTML tags and attributes to convey the meaning of your content. And, if possible, minimize the use of dangerouslySetInnerHTML
for large content blocks in your UI.
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>
Would you be interested in joining LogRocket's developer community?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowLearn how to manage memory leaks in Rust, avoid unsafe behavior, and use tools like weak references to ensure efficient programs.
Bypass anti-bot measures in Node.js with curl-impersonate. Learn how it mimics browsers to overcome bot detection for web scraping.
Handle frontend data discrepancies with eventual consistency using WebSockets, Docker Compose, and practical code examples.
Efficient initializing is crucial to smooth-running websites. One way to optimize that process is through lazy initialization in Rust 1.80.
2 Replies to "Using <code>dangerouslySetInnerHTML</code> in a React application"
great post
Nice post