Xen_mar
Xen_mar

Reputation: 9712

Can I render static html content in React without injecting styles?

I'm currently builidng a service that lets users create email templates. Users can preview the templates in the UI. The html for the preview is rendered on the server and send to the client. I currently render the "escaped" and "sanitized" content like this:

<div dangerouslySetInnerHTML={{ __html: digestPreview }} />

The problem with this approach is that the templates contain style tags like: <style>h1 {color: red}</style>.

This style will be injected into the rest of the page and messes up the rendering of all h1 tags on the screen. Not just the ones within the div that has the innerHTML prop set.

I'm looking for a way to render the html without injecting the styles. I don't want to convert all my styles to inline css.

Upvotes: 0

Views: 1136

Answers (2)

Oluwafemi Sule
Oluwafemi Sule

Reputation: 38962

Perhaps you can apply a filter on the digestPreview to remove the styles before passing the markup down.

const htmlString = `
<body>
<style>
h1 {color: red;}
</style>
<h1>hello</h1>
<p>
<style>
h1 {color: red;}
</style>
world
</p>
</body>
`;

function toStylelessDocument(htmlString) {
  const doc = (new DOMParser()).parseFromString(htmlString, "text/html");

  var treeWalker = document.createTreeWalker(
    doc,
    NodeFilter.SHOW_ELEMENT, {
      acceptNode: function(node) {
        if (node.tagName === 'STYLE') {
          return NodeFilter.FILTER_ACCEPT;
        }
      }
    },
    false
  );


  let currentNode = treeWalker.currentNode;
  const styles = [];
  while (currentNode) {
    if (currentNode.tagName === "STYLE") {
      styles.push(currentNode)
    }
    currentNode = treeWalker.nextNode();
  }
  for (let style of styles) {
    style.parentElement.removeChild(style);
  }
  return doc;
}

d = toStylelessDocument(htmlString);
console.log(d.body.innerHTML);

You can use the toStylelessDocument function create an HTMLDocument with the style elements removed from the DOMtree.

const stylelessDocument = toStylelessDocument(digestPreview);

<div dangerouslySetInnerHTML={{ __html: stylelessDocument.body.innerHTML }} />

Upvotes: 0

Vitalii Pedchenko
Vitalii Pedchenko

Reputation: 31

You can try using <iframe>.

An example:

import { useEffect, useRef } from "react";
import "./styles.css";

export default function App() {
  const iframeRef = useRef();

  useEffect(() => {
    const iframeDoc = iframeRef.current.contentWindow.document;
    iframeDoc.open("text/html", "replace");
    iframeDoc.write(
      "<style>h1 { color: red }</style><h1>Header inside iframe</h1>"
    );
    iframeDoc.close();
  }, []);

  return (
    <div className="App">
      <h1>Styled H1 in the app</h1>
      <iframe ref={iframeRef} src="about:blank" title="myIframe" />
    </div>
  );
}

Full available here: https://codesandbox.io/s/vibrant-drake-000u0x?file=/src/App.js.

Another way is to use Shadow DOM.

Upvotes: 3

Related Questions