3

I receive HTML from the server that needs to be injected via dangerouslySetInnerHTML. After inserting the HTML I want to get a DOM element with id: home_content.

This is because I migrate from a legacy application(server-side rendered HTML) to React, the legacy application returns HTML. The goal of this React component is to parse the HTML from the legacy application to a React component. Within this component, I want to add React components to the initial HTML structure received from the server.

export default function App() {
  const [data, setData] = useState({});
  const [isLoading, setIsLoading] = useState(true);
  const didMountRef = useRef(false);

  const pageName = useSetPageTitle();

  // Will be replaced by the actual API call, Add to legacy component
  useEffect(() => {
    const fetchData = async () => {
      const response = await fetch(
        `http://127.0.0.1:8000/nav?action=${pageName}`
      );
      const legacyHTML = await response.text();
      setData(legacyHTML);
      setIsLoading(false);
    };
    fetchData();

    didMountRef.current = true;
  }, [pageName]);

  useEffect(() => {
    if(didMountRef.current) {
      const domContainer = document.querySelector("#home_content");
      ReactDOM.render(<LikeButton />, domContainer);
    }
  }, [didMountRef]);

  return (
    <>
      <LegacyDependencies />
      <div>
        <div id="content"></div>
        {isLoading ? (
          <div>
            <span>loading...</span>
          </div>
        ) : (
          <div
            dangerouslySetInnerHTML={{
              __html: data,
            }}
          />
        )}
      </div>
    </>
  );
}

I receive the following error: Error: Target container is not a DOM element.

Is it possible to add a React component to HTML inserted by dangerouslySetInnerHTML?

5
  • Hope this helps ==> reactjs.org/docs/dom-elements.html#dangerouslysetinnerhtml Commented Jun 10, 2020 at 9:50
  • @Mohit this doesn't answer the question. Commented Jun 10, 2020 at 9:53
  • You were asking that is it possible to add the React component, for that I shared the link, because what html you're getting from the server it should be correct and yes you can inject it. Commented Jun 10, 2020 at 9:55
  • Your example shows how to parse HTML in a React component. The question is about adding a React component to the inserted HTML by dangerouslySetInnerHTML Commented Jun 10, 2020 at 10:01
  • Read it carefully, you need to pass the React component / html using __html attribute. Commented Jun 10, 2020 at 10:24

2 Answers 2

2

Here's how you could do it.

const { useRef, useState, useEffect } = React;

const getData = () => Promise.resolve('<div id="content">Test</div>')

const LikeButton = () => <button>Like!</button>

const App = () => {
  const ref = useRef();
  const [data, setData] = useState('');

  useEffect(() => {
    let isUnmounted = false
    getData()
      .then(pr => {
        if(isUnmounted) {
          return
        }
        
        setData(pr);
      })
      
    return () => {
      isUnmounted = true;
    }
  }, []);

  useEffect(() => {
    const element = ref && ref.current;

    if(element) {
     const content = document.getElementById("content");
     if(content) {
      ReactDOM.render(<LikeButton />, content);
     }
    }
  }, [ref, data])

return <div ref={ref} dangerouslySetInnerHTML={{
    __html: data,
  }}></div>
}

ReactDOM.render(
    <App />,
    document.getElementById('root')
  );
<script src="https://unpkg.com/react/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>
<div id="root"></div>

Sign up to request clarification or add additional context in comments.

Comments

1

A better way to go about this is with React Portals, it means the 'injected' component will still be a part of the same React component tree (aka you can access contexts, et. al). The docs for portals state:

A portal only changes the physical placement of the DOM node. In every other way, the JSX you render into a portal acts as a child node of the React component that renders it. For example, the child can access the context provided by the parent tree, and events bubble up from children to parents according to the React tree.

Here is a simplified working example.

import { useEffect, useRef, useState } from "react";
import { createPortal } from "react-dom";

export const SetInnerHtmlWithPortals: React.FC = () => {
  // The HTML we want to render as a string. In my case, this comes from
  // rendering a Handlebars template that's stored in our DB. I use the
  // Handlebars rendering step to create unique IDs on the elements I want to
  // inject React components into (and to track those IDs).
  const html = `
    <div>
      <div>Part of the HTML string</div>
      <div id="mount-here"></div>
    </div>
  `;

  // Store a ref to the element we'll 'dangerouslySetInnerHTML` on.
  const containerDiv = useRef<HTMLDivElement>(null);

  // This selects a mounted div from the HTML string, once the `containerDiv`
  // is set. That gets used in the portal below.
  const [innerDiv, setInnerDiv] = useState<Element | null>(null);
  useEffect(
    () =>
      setInnerDiv(containerDiv.current?.querySelector("#mount-here") ?? null),
    [containerDiv]
  );

  // Render the HTML string, and (once mounted) create a portal on the inner div
  return (
    <>
      <div ref={containerDiv} dangerouslySetInnerHTML={{ __html: html }} />
      {innerDiv && createPortal(<TestComponent />, innerDiv)}
    </>
  );
};

const TestComponent: React.FC = () => {
  return <div>Part of the React Portal</div>;
};

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.