Someone Special
Someone Special

Reputation: 13588

Embedding Reactjs in Remote Site iFrame

Updated for Bounty

I am currently using src="url" in my iframe to load the ReactApp which is not optimal for me as that will allow user to right-click and "Open In New Window".

The optimal solution which I'm looking for is to write my bundle.js into the iframe together with a or simliar solution. The src will remain as blank so user cannot conveniently right click to open in new window/tab.

I am exploring how to use React to make an embeddable widget so I got the following so far from googling. However, my doesn't render, and returns the following message.

Error Message [Error] Invariant Violation: Target container is not a DOM element.

I use create-react-app for my embeddable app and I have only 2 simple files.

index.js

import React from 'react';
import ReactDom from 'react-dom';
import App from './App';

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

App.js

import React, { Component } from 'react';

class App extends Component {
    render() {
        return (
            <div>This is an embedable widget</div>
            )
    }
}

export default App;

I created a test.js which will be called in the remote iframe with the simple codes below to generate the html, to include the js bundle, as well as the div with id.

This is the test.js

//Creates Div
var d = document.createElement("div");
document.body.appendChild(d);

//Creates iFrame
var n = document.createElement("iframe");
n.id = "microcom-frame";
n.style.width = "100%";
n.style.height = "100%";
n.style.background = "transparent";
n.style.position = "relative";
n.style.margin = 0;
n.style.border = "none";
n.style.overflow ="hidden";
n.style.display = "block";

//Append iFrame inside Div
d.appendChild(n);

//Write content to iFrame
n.contentWindow.document.open("text/html", "replace"), 
n.contentWindow.document.write("\n    <!doctype html>\n    <head><script src='http://localhost:3001/static/js/bundle.js' type='text/javascript'></script></head>\n    <body><div id='root'></div></body>\n    </html>"), 
n.contentWindow.document.close();

Now on the remote site I just have the following script in the header to call my test.js above.

    <script>
      (function() {
  var d = document,
      h = d.getElementsByTagName('head')[0],
      s = d.createElement('script');

  s.type = 'text/javascript';
  s.async = true;
  s.src = 'http://localhost:3001/test.js';
  h.appendChild(s);
  
} )();
</script>

Now is this....

However, is not displaying.

Appreciate someone can bring me to the next step. I am using create-react-app to create my react files (that's the only way i learnt.)

Upvotes: 9

Views: 23300

Answers (4)

You have used root as the target container in index.js. Probably this is not the name of DOM element in index.html file. Make it the same and probably it will work.

Upvotes: 1

H&#229;kan KA
H&#229;kan KA

Reputation: 323

The script fire before the target div is read in DOM in your test environment, just change the order of the tags. See below and see the bottom for other issues what you are trying to do will probably cause when moved to the wild.

Add "async defer" to the script tag you insert in the iframe in your your initial code like so:

n.contentWindow.document.write("\n    <!doctype html>\n    <head><script async defer src='http://localhost:3001/static/js/bundle.js' type='text/javascript'></script></head>\n    <body><div id='root'></div></body>\n    </html>"),

You can just add the script tag at the end of the body you insert instead (or do booth that and put async defer on it)

Try it with srcdoc, it works even better since it doesn't even give the option to break out of the frame (which just reloads the current location in the example above)

n.srcdoc = "\n    <!doctype html>\n    <head></head>\n    <body><div id='root'></div><script async defer  src='http://localhost:3001/static/js/bundle.js' type='text/javascript'></script></body>\n    </html>"

However.

When I tried this in production it didn't load my entire app. Anything other then the inital js bundle from the /static folder in build would not load because the app looks for them relative to the widgets location.

You might solve this by making sure the js does not chunk (create react app should not split your code yet anyway, unless you are using a package to do specifically that). You also need to load all css-files and images externally in your app rather then importing them.

It may also be possible to use one of the methods for deploying a react app into a sub-directory to set an absolute base url to your app's static resources.

Also you probably want to remove registerServiceWorker() from index.js in the react app when using it like this.

If that still causes issues and you need to load it by src you could have the react app check if it is not inside a frame and kill itself or go back when unframed by some of the methods here How to identify if a webpage is being loaded inside an iframe or directly into the browser window?

Upvotes: 1

Ramin Taghizada
Ramin Taghizada

Reputation: 125

Embedding in iframe is not a good practice as for security purposes, it could be better if you use sandboxed iframe .

Upvotes: 0

wizzwizz4
wizzwizz4

Reputation: 6426

To satisfy the Same Origin policy (prevent CORS errors), set the <iframe>'s srcdoc attribute instead of trying to write to it.

n.srcdoc = "\n    <!doctype html>\n    <head><script src='http://localhost:3001/static/js/bundle.js' type='text/javascript'></script></head>\n    <body><div id='root'></div></body>\n    </html>";

As an added bonus, you can disable the right-click context menu with:

n.contentWindow.document.addEventListener("contextmenu", function(e){
    e.preventDefault();
    return false;
}, false);

This is completely useless as a security measure, but it's only there as a red herring. The page that shows when you open the frame on its own will not contain the content. Only do this if there's nothing in the iframe's page that the user will want to copy; it doesn't stop them from doing so, but it's really irritating if you do want to copy something.


Demo:

//Creates Div
var d = document.createElement("div");
document.body.appendChild(d);

//Creates iFrame
var n = document.createElement("iframe");
n.id = "microcom-frame";
n.style.width = "100%";
n.style.height = "100%";
n.style.background = "transparent";
n.style.position = "relative";
n.style.margin = 0;
n.style.border = "none";
n.style.overflow ="hidden";
n.style.display = "block";

//Append iFrame inside Div
d.appendChild(n);

//Write content to iFrame
n.srcdoc = "<!doctype html><html><head></head><body><div id='root'>Example content</div></body></html>";

Upvotes: 5

Related Questions