Create framework agnostic components that are truly reusable and interoperable with all the benefits of the React ecosystem – using the HTML5 custom elements API to extend HTML's vocabulary.
Create framework agnostic components that are truly reusable and interoperable with all the benefits of the React ecosystem – using the HTML5 custom elements API to extend HTML's vocabulary.
npm install react-standalone --save
mars-weathercomponent for an idea on how to structure your reusable component – however essentially a component consists of a tag name — such as
mars-weather, the React
componentand an optional schema using
osom.
import { createModule } from 'standalone'; import schema from './schema'; import component from './component';export default createModule('mars-weather', { schema, component });
Once you have created your package, a custom element will be created with the supplied
tagNamewhich can be embedded into the DOM – all of the React lifecycle methods will be invoked, such as
componentWillUnmountwhen the element has been removed from the DOM.
As the
mars-weathercomponent is an entirely custom element, it can be embedded in any JavaScript framework — Angular, Vue, React, Cycle, Ember, Vanilla, etc...
Bonus: Use Keo with shadow boundaries for a true Polymer-esque feel.
By specifying attributes on the custom element, the values of the attributes are passed into your component as props – any changes to the
statewill be handled internally to your component, whereas any changes to your element's attributes will cause a re-render with the updated
props.
In the
mars-weatherexample, we have setup the
getDefaultPropsmethod to return the default props, however users can override the
unitprop by passing in a
dataattribute named
data-unit.
In the above case, the
data-unitattribute will be transformed to
unit— as
Standalonestrips away any
data-prefixes — and then re-renders your component, allowing you to access the attribute as
this.props.unit.
As all HTML attributes are
strings,
Standaloneallows you to specify a schema for your component, which will transform
stringattributes into the data type you expect using
osom.
export default { unit: { type: String, default: 'F' } };
Once you have configured the schema to use for your component, you can happily setup the usual React
propTypesspecifying the data type you're expecting to be passed through.
Using Custom Events you can easily set-up a communication channel between your components and the outside world.
// Instantiate `CustomEvent` and then specify the name of the event, followed // by the payload which will be passed to your listener function. const event = new CustomEvent('migrate-planets', { bubbles: true, detail: { planet: 'Saturn' } });findDOMNode(this).dispatchEvent(event);
It's crucial that you emit the event as
bubbles: trueotherwise the event would simply halt at the
findDOMNode(this)node rather than bubbling up to the
mars-weathernode — unless you dispatch the event on the
mars-weathernode by using
findDOMNode(this).parentNode.
Within your component you emit the event —
CustomEvent— using
dispatchEventand then bind your custom element — such as
mars-weather— using
addEventListenerfrom the outside.
const node = document.querySelector('mars-weather');node.addEventListener('migrate-planets', event => {
// Update the `data-planet` attribute to reflect the newly migrated planet // which will cause the component to re-render with the update prop. node.setAttribute('data-planet', event.detail.planet);
});
As invoking
setAttributeon your component causes React to re-render your component, it may be useful to supply a JSON payload to your component instead — especially if you're defining a multitude of attributes; this also helps with performance as you would only need one
setAttributeto update many props and re-render.
By defining a schema you can specify an attribute that will be parsed as JSON.
export default { payload: { type: JSON.parse } }
Attaching a JSON string to your element's
data-payloadattribute will cause it to be parsed into an object using
JSON.parse, and passed to your React component as
this.props.payloadwhich can be defined in the
propTypesusing
PropTypes.shape.
All
Standalonecomponents extend
HTMLElement.prototypeand allow for adding custom functions to the element — which you can invoke once you have a reference to the associated element. Take a look at
mars-weather's methods for an example.
const getWeather = function() { const weather = this.component.state.weather.atmoOpacity.toLowerCase(); return `The current weather on Mars is ${weather}!`; };// ...
document.querySelector('mars-weather').getWeather();
When a component has been appended to the DOM it will update its
HTMLElementprototype to assign the rendered component to
getPrototypeOf(this).component— this conveniently allows you to access the
propsand
state, and invoke functions internal to the React component.
It's worth noting that
this.componentwill only be available once the component has been appended to the DOM.
With the Custom Elements API it is possible to extend existing elements – using the
isattribute to specialise.
Standaloneallows you to extend elements by passing the element to extend.
// Creates a `mars-weather` element. export default createModule('mars-weather', { schema, methods, component });// Creates a
input[is="mars-weather"]
element. export default createModule('input/mars-weather', { schema, methods, component });
It's worth noting that when you extend a known element, your element will extend its prototype – in the case above the
mars-weatherelement will extend
inputand its
HTMLInputElementprototype.
* Requires the excellent webcomponents-lite.js polyfill (13K gzipped)