48

This is my senario :
1. Application request CMS(Content management system) for page contents.
2. CMS return "<div>Hi,<SpecialButton color="red">My Button</SpecialButton></div>"
3. Application consume the content, render corresponding component with data provided in attribute.

I can't figure out how to do step 3 in React way, any advice is appreciated.

Thanks @Glenn Reyes, here's a Sandbox to show the problem.

import React from 'react';
import { render } from 'react-dom';

const SpecialButton = ({ children, color }) => (
  <button style={{color}}>{children}</button>
);

const htmlFromCMS = `
<div>Hi, 
  <SpecialButton color="red">My Button</SpecialButton>
</div>`;

const App = () => (
  <div dangerouslySetInnerHTML={{__html: htmlFromCMS}}>
  </div>
);

// expect to be same as
// const App = () => (
//   <div>Hi, 
//     <SpecialButton color="red">My Button</SpecialButton>
//   </div>
// );

render(<App />, document.getElementById('root'));

Here is a live demo made by Vuejs. String "<div v-demo-widget></div>" could be treat as Vuejs directive and rendered. Source Code.

Sing
  • 3,942
  • 3
  • 29
  • 40
  • Don't have a definitive answer for you Andy, but might be able to point you in a direction, depending on how you get the CMS data. Have you tried using a higher order component to render the one that comes back from the request? I think a component that then renders your requested component might be the way to go. – Brett East Jun 20 '17 at 04:59
  • @BrettEast, a higher order component could do the request, but my problem is after getting the string like `

    Hello

    ` from CMS, how to let react know the part `` is component, need to excute component's code.
    – Sing Jun 20 '17 at 08:21
  • Yeah, that is tricky, any chance your incoming data follows react naming patterns, with an uppercase letter for react components and lowercase for html elements? Maybe a regex could do the trick? – Brett East Jun 20 '17 at 08:25
  • Yes I think it is ok to follows react naming patterns, but it looks like I need to write a compiler to do something like angular directive... – Sing Jun 20 '17 at 08:57

7 Answers7

53

You probably want to look deeper into dangerouslySetInnerHTML. Here is an example how to render HTML from a string in a React component:

import React from 'react';
import { render } from 'react-dom';

const htmlString = '<h1>Hello World! </h1>';

const App = () => (
  <div dangerouslySetInnerHTML={{ __html: htmlString }} />
);

render(<App />, document.getElementById('root'));

Full example here: https://codesandbox.io/s/xv40xXQzE

Read more about dangerouslySetInnerHTML in the React docs here: https://facebook.github.io/react/docs/dom-elements.html#dangerouslysetinnerhtml

glennreyes
  • 2,281
  • 16
  • 19
  • 2
    What if htmlString cotains a reactComponent string like `"

    Hello

    "` ? The `MyReactComponent` will be html string not a real component
    – Sing Jun 20 '17 at 08:56
  • Sorry guys, give me a second I make a full example to reduce misunderstanding. – Sing Jun 20 '17 at 09:00
  • 1
    Interesting, I get what you are looking for. I created a code sandbox here: https://codesandbox.io/s/WnKvoY6BE – glennreyes Jun 20 '17 at 09:27
  • Thank you, I have edit my question to include your sandbox with a little adjustment – Sing Jun 20 '17 at 10:50
  • Just noticed today. Feel free to mark this answer as solved, thanks! – glennreyes Jan 23 '18 at 10:17
  • I think it still not resolved my problem, the html string still unable to be treated as react component. – Sing Jan 23 '18 at 10:46
22

You can use the react-html-parser in case you don't want to use dangerouslySetInnerHTML attribute

import React from 'react';
import { render } from 'react-dom';
import ReactHtmlParser from 'react-html-parser';

const SpecialButton = ({ children, color }) => (
  <button style={{color}}>{children}</button>
);

const htmlFromCMS = `
<div>Hi, 
  <SpecialButton color="red">My Button</SpecialButton>
</div>`;

const App = () => (
  <div>
     {ReactHtmlParser(htmlFromCMS)}
  </div>
);


render(<App />, document.getElementById('root'));

Happy Coding!!!

kca
  • 4,856
  • 1
  • 20
  • 41
accimeesterlin
  • 4,528
  • 2
  • 25
  • 18
  • 4
    Does this Library use "dangerouslySetInnerHTML" in its background? – Omar Oct 09 '19 at 05:21
  • 6
    `react-html-parser` does not use `dangerouslySetInnerHTML` in the background; it uses [`htmlparser2`](https://www.npmjs.com/package/htmlparser2). – kca Jul 15 '20 at 12:09
  • 1
    The thing is that it didn't render the button itself. I had a specialbutton html element instead. The component wasn't rendered. `My Button` – Armalong Mar 12 '21 at 12:37
  • 5
    @AlekseyYaremenko For custom components, you have to use the `transform` option: https://github.com/peternewnham/react-html-parser/issues/64#issuecomment-501006825 – Daniel Loureiro Jun 26 '21 at 20:24
  • what would be the difference though. can't the input html still contain javascript that will then be executed, or does react-html-parser filter out things that can be dangerous? – Joel M Feb 20 '23 at 01:53
19

As pointed out in this answer by EsterlingAccimeYoutuber, you can use a parser in case you don't want to use dangerouslySetInnerHTML attribute.

By now, react-html-parser has not been updated for 3 years, so I went looking for a different module.

html-react-parser does same job but is frequently maintained and updated.

It should be good practice to sanitize your html-String to prevent XSS attacks. dompurify can be used for that.

I updated EsterlingAccimeYoutuber's code-example to the following:

import React from 'react';
import { render } from 'react-dom';
import parse from 'html-react-parser';
import DOMPurify from 'dompurify';

const SpecialButton = ({ children, color }) => (
  <button style={{color}}>{children}</button>
);

const htmlFromCMS = `
<div>Hi, 
  <SpecialButton color="red">My Button</SpecialButton>
</div>`;

const htmlFrom = (htmlString) => {
        const cleanHtmlString = DOMPurify.sanitize(htmlString,
          { USE_PROFILES: { html: true } });
        const html = parse(cleanHtmlString);
        return html;
}

const App = () => (
  <div>
     {htmlFromCMS && htmlFrom(htmlFromCMS)}
  </div>
);


render(<App />, document.getElementById('root'));

Inspired by original post above, hence special thanks to original authors!

Hans
  • 1,162
  • 11
  • 18
2

For any from the future just enhancement of GProst Answer, You can use ReactDOMserver, This is how we can implement the same.

import React from "react";
import { render } from "react-dom";
import { renderToString } from "react-dom/server";

const SpecialButton = ({ children, color }) => (
  <button style={{ color }}>{children}</button>
);

const renderButton = renderToString(<SpecialButton>MyButton</SpecialButton>);

const htmlFromCMS = `
<div>Hi, 
  ${renderButton}
</div>`;

const App = () => <div dangerouslySetInnerHTML={{ __html: htmlFromCMS }} />;

render(<App />, document.getElementById("root"));
lost_in_magento
  • 703
  • 8
  • 22
0

You can try use ReactDOMserver to render <MyReactComponent /> into html on your server and then pass it to the client, where you can insert all received html via dangerouslySetInnerHTML.

GProst
  • 9,229
  • 3
  • 25
  • 47
  • According to [ReactDOMServer](https://facebook.github.io/react/docs/react-dom-server.html), `RenderToString` accept a `component` as parameter not a string like "", so it could not be render correctly. – Sing Jun 20 '17 at 08:16
  • Sure, first of all, you need to find corresponding class by the component name. And then render it. I just assume it will be easily for you to do it on server side (in CMS). Otherwise you will need to parse the entire string, separating pure html and React component, then render them together inside another component. Anyways, that's not a trivial task, I suggest you to find some workaround. – GProst Jun 20 '17 at 08:38
  • I see, so your suggestion is the component logic put in CMS, and CMS return the rendered component string right ? – Sing Jun 20 '17 at 08:59
  • Yes, the most problem is in parsing the string. If you can get component name in CMS without big difficulty, then it will be easier to render component in CMS and then return it to the client. – GProst Jun 20 '17 at 09:08
  • Looks like no matter do it in client or CMS, I need a parser from html to react component – Sing Jun 20 '17 at 10:52
  • Sad, then yeah, parser is a way to deal with it. – GProst Jun 20 '17 at 10:58
  • `Vuejs` can do this simpler because it uses `html`. ReactJS uses JSX, which is different thing. – GProst Jun 20 '17 at 11:01
0

This is my way to use html-react-parser and react onClick event together.

import React from "react";
import { render } from "react-dom";
import parse from "html-react-parser";

const html = `
  <div style="font-size:32px;">html-react-parser with js events</div>
  <div>This is a long long long text.<div id="supportEmail"></div>t is a long established fact that a reader will be distracted by the readable content of a page when looking at its layout. The point of using Lorem Ipsum is that it has a more-or-less normal distribution of letters, as opposed to using 'Content here, content here', making it look like readable English.</div>
`;

const handlefunction = () => {
  alert("Clicked");
};

const replace = (domNode) => {
  if (domNode.attribs && domNode.attribs.id === "supportEmail") {
    return (
      <code>
        <div
          style={{
            backgroundColor: "gray",
            padding: "4px 8px",
            width: "100px",
            textAlign: "center"
          }}
          onClick={handlefunction}
        >
          Click
        </div>
      </code>
    );
  }
};

function App() {
  return parse(html, { replace });
}

render(<App />, document.getElementById("root"));

Check example in Codesandbox

Mike Chen
  • 1
  • 1
0

Simple and easiest way to achieve parser by using dangerouslySetInnerHTML attribute.

const htmlString = '<h1>Hello World! </h1>';
const App = () => (
  <div dangerouslySetInnerHTML={{ __html: htmlString }} />
);
Ramesh HB
  • 7
  • 4