dangerouslySetInnerHTML in a React application
Editor’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.
The Replay is a weekly newsletter for dev and engineering leaders.
Delivered once a week, it's your curated guide to the most important conversations around frontend dev, emerging AI tools, and the state of modern software.
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.
dangerouslySetInnerHTMLWhen 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.
dangerouslySetInnerHTMLSanitizing 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);
dangerouslySetInnerHTMLWith 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.
dangerouslySetInnerHTMLReact 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 payloaddangerouslySetInnerHTMLReact 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} />;
}
dangerouslySetInnerHTMLInstead 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.
dangerouslySetInnerHTMLApart 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>

Vibe coding isn’t just AI-assisted chaos. Here’s how to avoid insecure, unreadable code and turn your “vibes” into real developer productivity.

GitHub SpecKit brings structure to AI-assisted coding with a spec-driven workflow. Learn how to build a consistent, React-based project guided by clear specs and plans.

:has(), with examplesThe CSS :has() pseudo-class is a powerful new feature that lets you style parents, siblings, and more – writing cleaner, more dynamic CSS with less JavaScript.

Kombai AI converts Figma designs into clean, responsive frontend code. It helps developers build production-ready UIs faster while keeping design accuracy and code quality intact.
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 now
2 Replies to "Using <code>dangerouslySetInnerHTML</code> in a React application"
great post
Nice post