49

I building a small blog app using React and Redux. The blog show Posts page with title, author, tags and description of a post. When clicking on title or "read more" button, I want to load and render an HTML file with corresponding post from a local project's data folder with all the posts.

Redux is managing the state of the blog, loading initial posts.json file with 8 different posts, including htmlPath for the corresponding html file in the data folder.

Raj
  • 22,346
  • 14
  • 99
  • 142
Intermundos
  • 521
  • 1
  • 4
  • 7
  • Do you want to render the external html static and "as is" or is there any need to "parse" it somehow or make it interact with React (e.g component states) somehow? – Chris Oct 18 '16 at 12:57
  • Just render it, lets say I click on post's title "Hello World" and get all the post rendered from file hello-world.html – Intermundos Oct 18 '16 at 13:02
  • 2
    People will be pulling their hair at this one. It does not sound right. However, one starts to build app in React, expecting wonders, then gets to a point where one wants to do something that was easy, simple, unambiguous, straightforward to do with PHP. – Rolf Sep 21 '17 at 12:44

5 Answers5

29

The way I see it is that you have 2 problems to solve here. The first is how to set the innerHTML of an element in React. The other is how to get a specific HTML to render depending on a given variable (e.g the current route, the input of a textfield, etc).

1. Setting the innerHTML of an element

You can do this with the dangerouslySetInnerHTML prop. As the name suggests it sets the innerHTML of the said element to whatever you specify... and yes, the "dangerously" is accurate as it's intended to make you think twice before using this feature.

The Official Documentation reads as follows:

Improper use of the innerHTML can open you up to a cross-site scripting (XSS) attack. Sanitizing user input for display is notoriously error-prone, and failure to properly sanitize is one of the leading causes of web vulnerabilities on the internet.

Check out this Demo or the snippet below.

var Demo = React.createClass({

  getInitialState: function() {
    return {showExternalHTML: false};
  },
  
  render: function() {
    return (
      <div>
        <button onClick={this.toggleExternalHTML}>Toggle Html</button>
        {this.state.showExternalHTML ? <div>
          <div dangerouslySetInnerHTML={this.createMarkup()} ></div>
        </div> : null}
      </div>
    );
  },
  
  toggleExternalHTML: function() {
    this.setState({showExternalHTML: !this.state.showExternalHTML});
  },
  
  createMarkup: function() { 
    return {__html: '<div class="ext">Hello!</div>'};
  }

});

ReactDOM.render(
  <Demo />,
  document.getElementById('container')
);
.ext {
  margin-top: 20px;
  width: 100%;
  height: 100px;
  background: green;
  color: white;
  font-size: 40px;
  text-align: center;
  line-height: 100px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="container"></div>

2. Fetching the HTML from an external source

Note that the above example does not actually get the HTML from an external file, but is entered directly as a string.

One simple way to do dynamically fetch a choose a specific file would be to let your backend (e.g php) read the file from a local folder, parse the text, and send it back through an AJAX request.

Example

//Your React component
fetchExternalHTML: function(fileName) {
  Ajax.getJSON('/myAPI/getExternalHTML/' + fileName).then(
    response => {
      this.setState({
        extHTML: response
      });
    }, err => {
      //handle your error here
    }
  );
}
Community
  • 1
  • 1
Chris
  • 57,622
  • 19
  • 111
  • 137
  • 1
    Is there a safe and reccommended alternative to loading external HTML? – colecmc May 16 '18 at 20:21
  • 1
    @colecmc, not sure what you mean. What are you trying to achieve? – Chris May 17 '18 at 08:34
  • 1
    So I've got some html as a string, retrieved through a fetch call. With vanillaJS I would use `innerHTML` or `insertAdjacentHTML()` but with React, I'm not sure of the best approach. I'm considering turning the string to JSON and then adding components by looping through JSON. – colecmc May 17 '18 at 19:52
  • 1
    @colecmc, did you try method 1 above? – Chris May 17 '18 at 20:29
  • I will need to tweak my setup to make it work but I will give it a try. Thanks a million @Chris – colecmc May 18 '18 at 15:28
  • using innerHTML is not safe. – paddotk Feb 19 '20 at 09:24
  • @paddotk, can you elaborate? – Chris Feb 19 '20 at 11:33
  • @Chris innerHTML is prone to code injection. The React team decided to call this property (within React) `dangerouslySetInnerHTML` as a hint to the risk of using it. Read this bit for some further explanation: https://reactjs.org/docs/dom-elements.html#dangerouslysetinnerhtml – paddotk Feb 19 '20 at 13:01
  • @paddotk, my answer does involve the use of `innerHTML` at all. The first option uses `dangerouslySetInnerHTML` and I even explain of what it is and what it does. You are explaining to me what I already explained in my answer :P Not trying to be brash or anything, but did you read my whole answer? – Chris Feb 19 '20 at 13:57
  • It's one of two suggestions you give so it does involve (dangerouslySet)innerHTML..? I think it's only fair and necessary to mention that it's not safe. – paddotk Feb 19 '20 at 15:32
  • @paddotk, I agree but I do mention the *potential* risk of using it. What I am trying to understand is why it got downvoted. The risks are well emphasized in my answer. – Chris Feb 19 '20 at 15:46
  • @Chris Sorry about that, I guess I shouldn't be on stackoverflow so late in the evening :p. I did apparently not read it all the way through and thus wanna undo my downvote, however SO says my vote is locked :( Apologies! – paddotk Feb 21 '20 at 11:47
  • @paddotk No worries. You got me really confused there for a while :P – Chris Feb 21 '20 at 11:59
18

While Chris's answer was good, some more digging was required to make it work. Here are the steps that you need to take:

Add html loader to your project:

npm i -D html-loader

Add the following rule to your webpack.config file:

{
  test: /\.(html)$/,
  use: {
    loader: 'html-loader',
    options: {
      attrs: [':data-src']
    }
  }
}

Now you can import your html file as follow:

import React, { Component } from 'react';
import Page from './test.html';
var htmlDoc = {__html: Page};

export default class Doc extends Component {
  constructor(props){
    super(props);
  }

  render(){
     return (<div dangerouslySetInnerHTML={htmlDoc} />)
}}
Adam Boostani
  • 5,999
  • 9
  • 38
  • 44
  • I have done it EXACTLY as you described, but I get a string for the Page like "samples/myHtmlCode.html" – Oliver Watkins Jan 26 '18 at 11:12
  • Oh wait... I had the .html files in a test for some other loader as well. now it works – Oliver Watkins Jan 26 '18 at 11:13
  • this doesn't work for fetching html from the server, it only builds your webpack project with the html source as a data url (who would want to do this?) – r3wt Oct 09 '18 at 14:17
  • 12
    @r3wt and here i am again 1 year later to do exactly what i argued against above... – r3wt Oct 18 '19 at 17:21
  • It does not like the data-src : ValidationError: Invalid options object. HTML Loader has been initialized using an options object that does not match the API schema. - options has an unknown property 'attrs' – Oliver Watkins Jan 13 '22 at 01:37
  • 1
    Care to elaborate how to "Add the following rule to your webpack.config file" ? By default I don't even have a webpack.config file from create-react-app – Ovi Trif Mar 12 '22 at 13:32
0

If you really sure, get the post contents to frontend how you like from file system with server code, and show it in React component with dangerouslySetInnerHTML:

function createMarkup() { 
    return {__html: 'First &middot; Second'}; 
};

<div dangerouslySetInnerHTML={createMarkup()} />

More in docs: https://facebook.github.io/react/tips/dangerously-set-inner-html.html

  • I know about dangerouslySet, the thing I cant accomplish is when clicked on link, how to go with the path I have in store ../../data/posts/html/[name].html and load the content of the file into the view. – Intermundos Oct 18 '16 at 13:33
0

You can try react-templates. This is 'the' best available. You can have your template as an external file and can load it whenever you want and it'll render like charm with all the React API available.

Hope it helps.

Pranesh Ravi
  • 18,642
  • 9
  • 46
  • 70
0

Assuming the external pages to include are unknown at compile time of your app, and you don't need the app and the page to be able to interact (like, the page is a standalone HTML, for example rendered using a Markdown engine), iframe HTML element could do the job well. (More at https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe ).

Syntax is as follows :

<iframe src="link/to/your/html/page.html"></iframe>

And... Voilà, your page is displayed in the iframe.

It has some advantages :

  • It is super simple
  • It creates a new navigation context and thus "isolates" the external page from the app, making it relatively safe to use when displaying user-submitted content, at least in comparison to direct inclusion.

And some drawbacks :

  • Because of isolation, the CSS of your app will not be applied to the page, you have to include it manually in the page.
  • Likewise, you can't interact with page DOM from app. Nor the opposite.
  • Having multiple navigation contexts can be costly in comparison to direct inclusion in the app. This isn't a problem for a single page, but if you have hundredth of posts, you'll likely have to make sure that not all of their external pages are displayed at the same time (for example, make is so that only view more can be applied at a time.
  • Links within the page will not be affected by the eventual link resolution of your app. This can force you to maintain two different conventions when it comes to links and paths : one for the app, one for the pages.
Felix Bertoni
  • 400
  • 2
  • 10