Search code examples
javascriptreactjsgithub-pagesstatic-sitehugo

What is an elegant way to initialize multiple react-live components from a static website?


I'm currently creating a react component library that i want to provide as an npm package. I also want to provide a documentation that features fancy live rendering of the available components on e.g. github pages.

For the live editing feature i'm planning to use react-live which provides multiple react components to display a live editor and a preview. See an example from the styled-components docs how this looks like.

The react-live component accepts a string code containing the initial code that should be displayed in the editor and a list of components scope that can be used inside the live editor.

Now i want to use gohugo or a similar static site generator to deploy my documentation. I thought i could maybe provide a <div> inside my static site that has a special class react-live-demo and i will get these containers with document.getElementsByClassName('react-live-demo'), loop over them and render the react-live component into it.

I created a code snippet that shows a little example:

const {LiveProvider, LiveEditor, LiveError, LivePreview} = window['react-live'];

// a random component that i want to render in the live editor
const MyComponent = () => (
  <div>
    <h1>react live demo</h1>
    <p>This is a component from the script tag.</p>
  </div>
);

// a wrapper for the react-live editor
const Editor = ({code, scope}) => (
  <LiveProvider code={code} scope={scope}>
    <div className="live-example row">
      <div className="col-xs">
        <LiveEditor />
        <LiveError />
      </div>
      <div className="col-xs">
        <LivePreview />
      </div>
    </div>
  </LiveProvider>
);

// get all containers that have the initial code as innerHTML
const examples = document.querySelectorAll('script[data-name="react-live"]');

examples.forEach(example => {
  // insert a new div before the script tag
  const container = document.createElement('div');
  example.parentNode.insertBefore(container, example.nextSibling);
  
  // render the editor with the code from the script tag
  const code = example.innerHTML.trim();
  ReactDOM.render(<Editor code={code} scope={{MyComponent}} />, container);
});
.static-content {
  background-color: papayawhip;
}

.live-example {
  border: 1px solid orange;
}

.react-live-demo.code {
  display: none;
}

.invalid {
  color: red;
}
<p class="static-content">HERE IS SOME STATIC HTML CONTENT</p>

<script type="text/html" data-name="react-live">
<div>
  <h1>react live demo</h1>
  <p>This code is editable.</p>
</div>
</script>

<p class="static-content">SOME MORE STATIC HTML CONTENT</p>

<script type="text/html" data-name="react-live">
<MyComponent />
</script>





<link href="https://cdnjs.cloudflare.com/ajax/libs/flexboxgrid/6.3.1/flexboxgrid.min.css" rel="stylesheet"/>
<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>
<script src="https://unpkg.com/[email protected]/dist/react-live.min.js"></script>

The question is now:

How do embed the code string into my static site so that i can grab it with my js code and pass it to my react-live component?

I thought about <pre> or something like that. Or should i just embed it as inner html and read that? But then it would be displayed for short when the react component hasn't rendered yet. Or should i use some sort of script tag and make it available as globals? The key goal is to make it as easy as possible to add live editing examples to the documentation without touching any javascript code.

However if i pass something like <MyComponent /> as innerHTML this does of course not work as you can see in the code snippet.

Any best practice for that use case appreciated.

EDIT:

Based on the suggestion of @Daniel Alexandrov i edited the code snippet. The solution now reads the innerHTML of script tags with type="text/html set and creates a <div> container to insert the editor. This seems to work quite well. Any more ideas?


Solution

  • In my opinion the best choice is to use the <script> tag with a custom type attribute. Take a look at the Knockout.js template binding which uses type="text/html or the Angular <script type="text/ng-template">

    This way the browser will ignore the script tag completely, because it doesn't know how to interpret it.