How to inject a Gist with ReactJS
I am trying to inject a Gist using ReactJS, but I am getting the following error:
Failed to execute 'write' on 'Document': Cannot write to document from an asynchronously loaded external script unless it is explicitly open.
Here's my component:
var EmbeddedGist = React.createClass({
render: function() {
return (
<div id="gist-container" />
);
},
componentDidMount: function() {
var src = this.props.srcUrl + ".js";
$('#gist-container').html('<script src="' + src + '"></script>');
}
});
I am calling it from another component, as such:
<EmbeddedGist srcUrl="https://gist.github.com/awalGarg/a5bd02978cecf3703f61" />
Any ideas on how to make this work?
source to share
The embed gist script uses document.write
to write HTML that composes an inline Gist into an HTML document. However, by the time your React component adds the tag script
, it's too late to write to the document.
While you cannot add a Gist embed script to your page dynamically, you can get the content of the Gist via JSONP and write that to the document yourself. Here's a component that takes a property gist
and an optional property file
and gives you the gist.
var EmbeddedGist = React.createClass({
propTypes: {
gist: React.PropTypes.string.isRequired, // e.g. "username/id"
file: React.PropTypes.string // to embed a single specific file from the gist
},
statics: {
// Each time we request a Gist, we'll need to generate a new
// global function name to serve as the JSONP callback.
gistCallbackId: 0,
nextGistCallback: function() {
return "embed_gist_callback_" + EmbeddedGist.gistCallbackId++;
},
// The Gist JSON data includes a stylesheet to add to the page
// to make it look correct. `addStylesheet` ensures we only add
// the stylesheet one time.
stylesheetAdded: false,
addStylesheet: function(href) {
if (!EmbeddedGist.stylesheetAdded) {
EmbeddedGist.stylesheetAdded = true;
var link = document.createElement('link');
link.type = "text/css";
link.rel = "stylesheet";
link.href = href;
document.head.appendChild(link);
}
}
},
getInitialState: function() {
return {
loading: true,
src: ""
};
},
componentDidMount: function() {
// Create a JSONP callback that will set our state
// with the data that comes back from the Gist site
var gistCallback = EmbeddedGist.nextGistCallback();
window[gistCallback] = function(gist) {
if (this.isMounted()) {
this.setState({
loading: false,
src: gist.div
});
EmbeddedGist.addStylesheet(gist.stylesheet);
}
}.bind(this);
var url = "https://gist.github.com/" + this.props.gist + ".json?callback=" + gistCallback;
if (this.props.file) {
url += "&file=" + this.props.file;
}
// Add the JSONP script tag to the document.
var script = document.createElement('script');
script.type = 'text/javascript';
script.src = url;
document.head.appendChild(script);
},
render() {
if (this.state.loading) {
return <div>loading...</div>;
} else {
return <div dangerouslySetInnerHTML={{__html: this.state.src}} />;
}
}
});
You can use it like this:
var app = (
<div>
<EmbeddedGist gist="BinaryMuse/a57ae1a551472e06b29a" file="restful.js" />
<hr />
<EmbeddedGist gist="BinaryMuse/bb9f2cbf692e6cfa4841" />
</div>
);
React.render(app, document.getElementById("container"));
Take a look at this JSfiddle working example : http://jsfiddle.net/BinaryMuse/nrb6zxfw/
One way to improve this is to ensure that an existing component EmbeddedGist
that has a change gist
or file
prop is updated to use the new data by connecting to componentWillReceiveProps
.
source to share
I have implemented it with updating props using componentWillRecieveProps like those suggested by binaryMuse.
Something like that:
componentDidMount: function() {
this._getGistData(this.props.gist,this.props.file);
},
componentWillReceiveProps: function (nextProps) {
if (this.props.gist !== nextProps.gist || this.props.file !== nextProps.file) {
this._getGistData(nextProps.gist,nextProps.file);
}
},
Where _getGistData is just the source code that was in the componentDidMount
source to share