Using React with Web Components

7/2/2017

Dealing with legacy code can be tough, even more so if you want to slowly migrate your front end to React.

Recently I found myself in a situation where some HTML was rendered server side, and inserted into the browser on request. I wanted to use a shiny new React component, instead of falling back to the legacy jQuery code.

In comes Web Components!

Web Components allow you to write your own HTML tags, and use them through out your application. This solves my problem, by having the server render the new HTML tag, and the front end automatically load the required JavaScript to render/control it. When rendering the Web Component we can delegate the actual rendering to React.

Lets start with a simple React component:

import React from "react";

class MyComponent extends React.Component {
  render() {
    return <div>Hello world! This was rendered using React!</div>;
  }
}

In order to render the component, we have to create a new HTMLElement as so:

class CustomHTMLElement extends HTMLElement {
  connectedCallback() {
    ReactDOM.render(<MyComponent />, this);
  }
}

In this, when the component is loaded by the browser, the connectedCallback will be called, allowing us to render the new component, at this (the location of the element in the DOM).

To finish, we just have to register the element with the customElements API:

customElements.define("x-test", CustomHTMLElement);

Now we can just use the tag <x-test/> in our HTML, to render the React component!

One problem with this setup can be Babel, as it doesn't play nice with transforming classes and extending HTMLElement. We can work around this using a custom class, and extending the prototype of HTMLElement:

function BabelHTMLElement() {
  const newTarget = Object.getPrototypeOf(this).constructor;
  return Reflect.construct(HTMLElement, [], newTarget);
}

Object.setPrototypeOf(BabelHTMLElement.prototype, HTMLElement.prototype);

class CustomHTMLElement extends BabelHTMLElement {
    ...

Now our CustomHTMLElement extends the BabelHTMLElement rather the HTMLElement directly.

To view an example project with this in use, you can checkout the repo here: https://github.com/Amwam/react-webcomponents