stenciljs-in-react

Stencil is not a JS framework. It is a compiler that produces a reusable web component that can be embedded anywhere else.

Usage no npm install needed!

<script type="module">
  import stenciljsInReact from 'https://cdn.skypack.dev/stenciljs-in-react';
</script>

README

Stencil components in React

Stencil is not a JS framework. It is a compiler that produces a reusable web component that can be embedded anywhere else.

This is a step by step guide to consume a non-trivial stencil component in a React app.

The starter react app was created with create-react-app.

Similar guides

Table of contents

0: Build a stenciljs component and publish it to npm

Creating your first stencil component is very easy and it is well documented here.

This example will consume two components:

1: Add the component(s) to the dependencies

Add the component to the app dependencies in package.json

// package.json

"dependencies": {
  ...
  "@openchemistry/molecule-vtkjs": "^0.1.9",
  "split-me": "^0.3.1"
}

2: Import the component(s)

Import the component in the index.js of the app:

import { defineCustomElements as defineMolecule } from '@openchemistry/molecule-vtkjs';
import { defineCustomElements as defineSplitMe } from 'split-me';

defineMolecule(window);
defineSplitMe(window);

3: Consume the component

It is now possible to use the tag provided by the stencil component in the render function of any react component.

render() {
    return (
      <split-me n="2">
        <oc-molecule-vtkjs slot="0"></oc-molecule-vtkjs>
        <oc-molecule-vtkjs slot="1"></oc-molecule-vtkjs>
      </split-me>
    )
}

Appendix: Attribute vs Prop

oc-molecule-vtkjs has a property named cjson that expects an object (or a JSON.stringified object).

Strings and numbers can be passed directly as attributes to a stencil component.

One way to pass a complex object to a component could be to JSON.stringify() the object and then JSON.parse() it inside the component. But this round trip can be expensive, and it would be a good idea to pass the object directly as a prop.

React doesn't provide a convenient way to distinguish between attribute and prop, so a little work is needed to achieve this.

It just boils down to saving a reference to the element of the stencil component, and then set the property directly in the javascript code.

To make this operation easier, it can be convenient to create a reusable utility function wc.

export function wc(customEvents = {}, props = {}) {
  let storedEl;

  return function (el) {
    for (let name in customEvents) {
      let value = customEvents[name] ;
      // If we have an element then add event listeners
      // otherwise remove the event listener
      const action = (el) ? el.addEventListener : storedEl.removeEventListener;
      if (typeof value === 'function') {
        action(name, value);
        return;
      }
    }
    
    // If we have an element then set props
    if (el) {
      for (let name in props) {
        let value = props[name] ;
        el[name] = value;
      }
    }
    storedEl = el;
  };
}

And then use it in the jsx to bind events and properties to the webcomponent this way:

import React, { Component } from 'react';
import { wc } from './utils/webcomponent';

class SomeComponent extends Component {

  render() {
    return (
      <div style={{width: "100%", height: "20rem", position: "relative"}}>
        <oc-molecule-vtkjs
          ref={wc(
            // Events
            {},
            // Props
            {
              cjson: molecule
            }
          )}
        />
      </div>
    );
  }

}

export default SomeComponent;