Matt Valley

Software Developer
Javascript (React, Node.js)

Read this before using dangerouslySetInnerHTML

As documented in React website, dangerouslySetInnerHTML is the React equivalent for browser’s innerHTML API. It’s named this way because generally speaking it’s not a good idea to render user input as HTML as it can cause some security issues but the truth is that sometimes it makes sense to use dangerouslySetInnerHTML. Take this website as an example, it’s built using Gatsby.js and I have used WordPress as CMS. In this tutorial, I’m going to bring your attention to one edge case that I came across while using dangerouslySetInnerHTML to render post excerpts.

As mentioned earlier, I’m using WordPress as CMS and source the WordPress content to my website using gatsby-source-wordpress plugin. To fetch blog posts, I execute the following GraphQL query:

{
  allWordpressPost {
    edges {
      node {
        title
        content
        slug
        date
        excerpt
        featured_media {
          source_url
        }
      }
    }
  }
}

The local version of my website was behaving fine (I use npm run develop) but the production build (using npm run build) had one super weird issue: post excerpts would render and immediately disappear! After debugging this issue for a good two hours, I narrowed it down to one thing: dangerouslySetInnerHTML! The following code behaves weirdly:

const StyledExcerpt = styled.p`
  /* the code to make it fancy */
`;

<Excerpt dangerouslySetInnerHTML={{ __html: post.excerpt }} />

but the below code would work just fine:

const StyledExcerpt = styled.p`
  /* the code to make it fancy */
`;

<Excerpt>{post.excerpt}</Excerpt>

After a bit of research, I came across this Github issue and this answer. Turns out the reason why dangerouslySetInnerHTML is behaving weirdly is that the Excerpt component is a styled P and post.excerpt contains “p” as well. Apparently that’s not OK as P tag is not supposed to be a child of another P as discussed here.

The P element represents a paragraph. It cannot contain block-level elements (including P itself).

HTML Specification

The way I fixed this problem was to use “div” tag for “Excerpt” component instead of P. The following code works with no issue:

const StyledExcerpt = styled.div`
  /* the code to make it fancy */
`;

<Excerpt dangerouslySetInnerHTML={{ __html: post.excerpt }} />

Honestly, I don’t think this is an issue with dangerouslySetInnerHTML but simply a limitation of the P tag. However, the fact that the behaviour of dangerouslySetInnerHTML is different in dev & production builds is a matter of question for me! I was expecting to see the same behaviour here.