VOOZH about

URL: https://blog.logrocket.com/using-dangerouslysetinnerhtml-react-application/

⇱ Using dangerouslySetInnerHTML in a React application - LogRocket Blog


2024-09-13
1950
#react
Doğacan Bilgili
58019
👁 Image

See how LogRocket's Galileo AI surfaces the most severe issues for you

No signup required

Check it out

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.

👁 Using DangerouslySetInnerHTML In A React Application

🚀 Sign up for The Replay newsletter

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.

What is 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.

When to use 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 risks

Using 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:

  • DOM-based XSS: These attacks happen from the client and, like the example we gave above, 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 XSS: Attacks that can happen when rendering user-submitted content. 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.


Over 200k developers use LogRocket to create better digital experiences

👁 Image
Learn more →

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.

Sanitization when using 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);

Impact of React 18+ on 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.

React Server Components and 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:

  • Security: Always implement and maintain a strict Content Security Policy to mitigate the risk of XSS attacks, even when using dangerouslySetInnerHTML. Server-side rendering (SSR) with dangerouslySetInnerHTML can still expose you to XSS attacks if the content isn’t properly sanitized
  • Hydration: When the server-rendered content is hydrated on the client, React needs to match the server-rendered DOM with the virtual DOM. Using dangerouslySetInnerHTML can complicate this process, potentially leading to hydration errors if the content changes between server and client renders
  • Performance: While Server Components can improve performance by reducing client-side JavaScript, overuse of dangerouslySetInnerHTML can negate some of these benefits by increasing the size of the initial HTML payload

Concurrent rendering and dangerouslySetInnerHTML

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} />;
}

Alternative to 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.


More great articles from LogRocket:


SEO considerations when using dangerouslySetInnerHTML

Apart from the existing security and performance considerations, using dangerouslySetInnerHTML can also have significant implications for SEO:

  • Async content: Search engine crawlers can generally read and index content rendered via dangerouslySetInnerHTML. However, if the content is loaded asynchronously or requires JavaScript execution, it may not be immediately visible to crawlers
  • Page speed: Speed is also a known ranking factor for search engines and an excessive use of dangerouslySetInnerHTML can impact page load times, especially if large chunks of HTML are being inserted

One 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.

Conclusion

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.

Get set up with LogRocket's modern React error tracking in minutes:

  1. Visit https://logrocket.com/signup/ to get an app ID
  2. 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>
     
  3. (Optional) Install plugins for deeper integrations with your stack:
    • Redux middleware
    • NgRx middleware
    • Vuex plugin
Get started now
👁 Image
👁 Image
👁 Image

Stop guessing about your digital experience with LogRocket

Get started for free

Recent posts:

How to add authentication to a React Native app with Better Auth

Learn how to build a full React Native auth system using Better Auth and Expo — with email/password login, Google OAuth, session persistence, and protected routes.

👁 Image
Chinwike Maduabuchi
Jun 9, 2026 ⋅ 13 min read

AI dev tool power rankings & comparison [June 2026]

Compare the top AI development tools and models of June 2026. View updated rankings, feature breakdowns, and find the best fit for you.

👁 Image
Chizaram Ken
Jun 8, 2026 ⋅ 11 min read

How to check username availability at scale with Bloom filters

Learn how Bloom filters reduce database lookups for username availability checks while preserving correctness at scale.

👁 Image
Rosario De Chiara
Jun 8, 2026 ⋅ 6 min read

An advanced guide to Nuxt testing and mocking

Learn how to test Nuxt apps with Vitest, @nuxt/test-utils, runtime mocks, server route mocks, and Playwright e2e tests.

👁 Image
Sebastian Weber
Jun 5, 2026 ⋅ 15 min read
View all posts

Hey there, want to help make our blog better?

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