Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to embed a Gist using ReactJS

Tags:

reactjs

gist

I'm trying to embed a Gist using ReactJS, but I'm getting the following error:

Failed to execute 'write' on 'Document': It isn't possible to write into a document from an asynchronously-loaded external script unless it is explicitly opened.

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'm calling it from within another component, as such:

<EmbeddedGist srcUrl="https://gist.github.com/awalGarg/a5bd02978cecf3703f61" />

Any ideas on how to make this work?

like image 561
Steven Mercatante Avatar asked Dec 10 '22 23:12

Steven Mercatante


2 Answers

The Gist embed script uses document.write to write the HTML that makes up the embedded Gist into the document's HTML. However, by the time your React component adds the script tag, it's too late to write to the document.

While you can't add the Gist embed script to your page dynamically, you can get the contents of the Gist via JSONP and write that into the document yourself. Here's a component that takes a gist property and an optional file property and renders the gist for you.

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 example of it working on JSfiddle: http://jsfiddle.net/BinaryMuse/nrb6zxfw/

One way to improve this would to ensure that an existing EmbeddedGist component that has its gist or file prop change would update to use the new data by hooking into componentWillReceiveProps.

like image 122
Michelle Tilley Avatar answered Dec 24 '22 18:12

Michelle Tilley


I implemented it with updating of the props using componentWillRecieveProps like binaryMuse suggested.

Did something like this:

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 simply the original code that was in componentDidMount

like image 26
Kinnza Avatar answered Dec 24 '22 17:12

Kinnza