DEV Community

Cory Rylan
Cory Rylan

Posted on • Originally published at coryrylan.com

Using Web Components in React

This post is a modified excerpt chapter from my new EBook Web Component Essentials

This post we will learn how to use Web Components in ReactJS.
React is a JavaScript library made by Facebook that allows developers
to compose UIs with components. React was one of the first JavaScript library/frameworks to popularize component driven architecture. React was also created before the Web Components APIs were standardized. Because of this, React does not have broad support for Web Components/Custom Elements like the majority of other JavaScript libraries and frameworks.

Web Components in Angular and VueJS

When using Web Components in other frameworks like Angular or Vue, we get built-in native support. In this post, we will use a vanilla dropdown Web Component. We won't go into the implementation of the dropdown component, but if you want more info on how to build your own Web Components check out the following posts:

Introduction to Web Components

Building Web Components with lit-html

Web Component working in ReactJS

Web Components can be used anywhere and work just like regular HTML elements when using plain JavaScript.

<!doctype html>
<html>
  <head>
  </head>
  <body>
    <x-dropdown>
      Hello World
    </x-dropdown>

    <script type="module">
      import '/dropdown.js';

      const dropdown = document.querySelector('x-dropdown');
      dropdown.title = 'Custom Title';
      dropdown.addEventListener('show', e => console.log(e));
    </script>
  </body>
</html>

Elements in HTML can communicate via properties, attributes, content slots and events. When using Web Components in Angular or Vue, we simply import the component file or package and use it in the template.

<h1>Angular Application using Web Components</h1>

<p>
  {{open ? 'open' : 'closed'}}
</p>

<x-dropdown [title]="myTitle" (show)="toggle($event)">
  Hello from Web Component in Angular!
</x-dropdown>
<h1>VusJS Application using Web Components</h1>

<p>
  {{show ? 'open' : 'closed'}}
</p>

<x-dropdown :title="myTitle" @show="log">
  Hello from Web Component in Vue!
</x-dropdown>

Both Angular and Vue have a native binding syntax that allows you to set properties and listen to events on native Custom Elements/Web Components. Unfortunately React is incompatible with setting properties and listening to custom events in the browser.

React Compatibility

React uses a similar mechanism for component communication by passing properties and functions as events between components. Unfortunately, the React event system is a synthetic system that does not use the built-in browser custom events. This synthetic system means Web Component events cannot communicate with React components. React, and the JSX templating syntax it uses treats all custom element properties as attributes incorrectly forcing React users only to use string values without additional work.

To overcome these shortcomings in our example, we will show how we can create thin React wrapper components around our Web Components. Wrapper components will allow React to be able to become compatible with our Web Components.

Create React App

To demonstrate Web Components in React, we will use the Create React App CLI tool to easily create a React application. To create our app, we run the following commands:

npx create-react-app my-app
cd my-app
npm start

Once created we will have a full running React application. Now we need to install a Web Component. I published a basic dropdown component to NPM that we can use as an example.

npm install web-component-essentials --save

In our React application, we will need to create a React Dropdown component to wrap our existing x-dropdown component.

import React, { Component } from 'react';
import 'web-component-essentials';

export class Dropdown extends Component {
  render() {
    return (
      <x-dropdown>
        {this.props.children}
      </x-dropdown>
    )
  }
}

To use our x-dropdown, we import the package into the Dropdown.js
React component. In the render function, we add {this.props.children} to pass child elements into our content slot.

Properties and Events

We need to map the Web Component properties and events to our React version of the component. We need to use the componentDidMount() life cycle hook.

import React, { Component } from 'react';
import 'web-component-essentials';

export class Dropdown extends Component {
  constructor(props) {
    super(props);
    this.dropdownRef = React.createRef();
  }

  componentDidMount() {
    this.dropdownRef.current.title = this.props.title;

    if (this.props.onShow) {
      this.dropdownRef.current.addEventListener('show', (e) => this.props.onShow(e));
    }
  }

  render() {
    return (
      <x-dropdown ref={this.dropdownRef}>
        {this.props.children}
      </x-dropdown>
    )
  }
}

Using the Refs API, we can grab a DOM reference to our x-dropdown. Using this reference, we can create our event listener. In our event listener, we can call any passed functions to our onShow prop for our react component. This allows our Web Component to be able to communicate with other React components. We also assign the title prop of our React dropdown to our Web Component property.

// current gets the current DOM element attached to the ref
this.dropdownRef.current.title = this.props.title;

Prop Updates

Next, we need to add additional code for whenever one of the props on our React dropdown change. To listen for prop updates we can use the componentWillReceiveProps() lifecycle hook.

import React, { Component } from 'react';
import 'web-component-essentials';

export class Dropdown extends Component {
  constructor(props) {
    super(props);
    this.dropdownRef = React.createRef();
  }

  componentDidMount() {
    this.dropdownRef.current.title = this.props.title;

    if (this.props.onShow) {
      this.dropdownRef.current.addEventListener('show', (e) => this.props.onShow(e));
    }
  }

  componentWillReceiveProps(props) {
    if (props.title !== this.props.title) {
      this.dropdownRef.current.title = props.title;
    }

    if (props.show !== this.props.show) {
      this.dropdownRef.current.show = props.show;
    }
  }

  render() {
    return (
      <x-dropdown ref={this.dropdownRef}>
        {this.props.children}
      </x-dropdown>
    )
  }
}

Using componentWillReceiveProps() we can check when props are updated
and efficiently update the properties on our Web Component. Now that we have mapped React props to our Web Component properties and events we can use the Dropdown React component.

import React, { Component } from 'react';
import './App.css';
import { Dropdown } from './dropdown.js';

class App extends Component {
  constructor(props) {
    super(props);
    this.state = {
      show: false,
      title: 'project-react'
    };

    this.handleShow = this.handleShow.bind(this);
  }

  render() {
    return (
      <div>
        <h1>React Application using Web Components</h1>

        <p>
          {this.state.show ? 'open' : 'closed'}
        </p>

        <Dropdown title={this.state.title} onShow={this.handleShow}>
          Hello from dropdown
        </Dropdown>
      </div>
    );
  }

  handleShow(e) {
    this.setState({ show: e.detail });
  }
}

export default App;

Now we should see our rendered Web Component working in a React application.

Example Web Component in React JS

In our App component, you can see the syntax is not much different than
our Angular and Vue examples. Unfortunately due to the incompatibility of
React with the Custom Elements API we have to add a thin compatibility
layer between our component.

Hopefully soon React will be able to adapt and become compatible with the Custom Elements API. To follow the status of the open React issues related to Web Components check out custom-elements-everywhere.com.

Find the full working demo here!

Top comments (0)