0

I have HTML documents that I fetch from my backend. Those documents consist of 2 kinds of tags and I want to render them dynamically. This is my source code

const Content = ({ slug }) => {
  const [data, setData] = useState()
  useScript("https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js");

  useEffect(() => {

    api.posts.read({ slug: slug }, { formats: ["html", "plaintext"] }).then((resp) => {
      setData(resp)
    })
  }, [slug])

  if (data) {
    return (
      <>
        <SEO
          title={data.title}
          description={data.meta_description}
          canonical_url={data.canonical_url}/>
          <section className={'content'}>
            <h4 className={'content-title'}>{data.title}</h4>
            <div dangerouslySetInnerHTML={{ __html: data.html }}/>
          </section>
      </>
      )
  }
  return <Section>
    <CircularProgress style={{color: '#004D80'}}/>
    <p>Loading</p>
  </Section>
}

I tried these 2 but none of them works for my use case.

1 <script> is rendered but not executed

<div dangerouslySetInnerHTML={{ __html: data.html }}/>

2

React: Script tag not working when inserted using dangerouslySetInnerHTML

This is not working for my case. What if I have tags like <script src="https://gist.github.com/ogyalcin/66d0785998588ab50cf1908f8d43bb7b.js"></script> in order to render a code block between two paragraphs? Besides, it is hard to handle if there are more attributes inside the tag.

Community
  • 1
  • 1
Lee Yee Run
  • 77
  • 3
  • 14
  • 1
    well, it's purposefully set [not to be executed](https://github.com/facebook/react/blob/72d00ab623502983ebd7ac0756cf2787df109811/packages/react-dom/src/client/ReactDOMComponent.js#L448) – Agney Apr 13 '20 at 04:35
  • Ya, is that any workarounds? – Lee Yee Run Apr 13 '20 at 04:45
  • Ref: https://stackoverflow.com/questions/6432984/how-to-add-a-script-element-to-the-dom-and-execute-its-code – stormwild Apr 13 '20 at 05:06

2 Answers2

0

Not an answer, just a bit of feedback FYI. I did an experiment inside browser. Seems like the script tag is rendered. However the code inside isn't executed, not sure why.

// @jsx

function App() {
  return <div><h1>React</h1><div dangerouslySetInnerHTML={{ __html: window.thatScript }} /><h2>JS</h2></div>
}

ReactDOM.render(<App />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id='root'></div>
<script>
window.thatScript = '<script type="text/javascript">function foo() {console.log("yolo")} setTimeout(() => foo()};<\script>'
</script>
hackape
  • 18,643
  • 2
  • 29
  • 57
  • 1
    This is because script tags injected via innerHTML are not executed for security reasons: https://developer.mozilla.org/en-US/docs/Web/API/Element/innerHTML Reference: https://github.com/facebook/react/issues/8838 – stormwild Apr 13 '20 at 04:58
0

May help:

Sample code from a create-react-app project.

import React, { useEffect } from 'react';
import './App.css';

function App() {
  const danger = '<script>alert("Hello world!")</script>';

  useEffect(() => {
    const el = document.querySelector('.danger');
    el.appendChild(document.createRange().createContextualFragment(danger));
  }, []);

  return (
    <div className='App'>
      <h1>Render and execute script tag</h1>
      <div className='danger'></div>
    </div>
  );
}

export default App;

Edit trusting-dream-i9ozk

There may also be a library for that:

dangerously-set-html-content

import React from 'react'
import InnerHTML from 'dangerously-set-html-content'

function Example {

  const html = `
    <div>This wil be rendered</div>
    <script>
      alert('testing')
    </script>
  `

  return (
    <InnerHTML html={html} />
  )
}

This will also work for scripts with the src attribute set it

Sharing good article for more information: Render dangerous content with React

Render the danger

Now what happens when you want to use dangerouslySetInnerHTML but also need to execute any script tag that comes inside the html? That's against HTML5 specifications, but if we dig a little bit more on what innerHTML do to inject the html we can found something interesting:

The specified value is parsed as HTML or XML (based on the document type), resulting in a DocumentFragment object representing the new set of DOM nodes for the new elements.

This DocumentFragment is a lightweight version of the document, it can have child nodes, the main difference is that since is a fragment, is not actually a part of the active/main document.

We can create a DocumentFragment using the document.Range API.

import React, { useEffect, useRef } from 'react'

// InnerHTML component
function InnerHTML(props) {
  const { html } = props
  const divRef = useRef(null)

  useEffect(() => {
    const parsedHTML = document.createRange().createContextualFragment> (html)
    divRef.current.appendChild(parsedHTML)
  }, [])


  return (
    <div ref={divRef}></div>
  )
}

// Usage
function App() {
  const html = `
    <h1>Fragment</h1>
  `

  return (
    <InnerHTML html={html} />
  )
}

Reference on innerHTML

stormwild
  • 2,855
  • 2
  • 31
  • 38