Thomas Derflinger BlogFR

LitElement Web Components in React App

LitElement is a Web Component library by Google. It succeeds the Polymer library and strives for speed and compatibility with the Web Components standard. How to integrate LitElements into your React application and use event handling I want to show in this article.

The final application can be downloaded from my GitHub repository (https://github.com/tderflinger/react-litelement-example).

About LitElement

LitElement, together with lit-html form the latest round of libraries from Google that adhere to the Web Components standard.

Web Components allow you to extend HTML and are supported by all modern browsers. For legacy browsers there are polyfill libraries available.

Breaking up your application into these building blocks makes it more maintainable and more reusable.

If the concept of Web Components sounds familiar to React developers, it is. In React applications programmers also create components, but the technology behind the two is different.

Nonetheless, you can use LitElement Web Components in your React application.

Preparing the React Application

As a starting point for the example I created a new React application with Create React App. In order to use LitElement and lit-html, I add the two libraries:

yarn add lit-element lit-html --save

Then, because the LitElement example uses JavaScript decorators, I add react-app-rewired and the Babel decorator proposal (@babel/plugin-proposal-decorators).

You can follow the procedure in the article by Michiel Sikma.

Integrating a LitElement

The example LitElement I use is the Wokwi push button. It was developed by Uri Shaked as part of his Wokwi project of Arduino electronics parts in JavaScript.

I adapted the pushbutton-element.js a bit but left the functionality essentially unchanged.

pushbutton-element.js, adapted

import { css, customElement, html, LitElement, property } from 'lit-element';
import { SPACE_KEYS } from './utils/keys';

@customElement('wokwi-pushbutton')
class PushbuttonElement extends LitElement {
  @property() color = 'red';
  @property() pressed = false;

  static get styles() {
    return css`
      button {
        border: none;
        background: none;
        padding: 0;
        margin: 0;
        text-decoration: none;
        -webkit-appearance: none;
        -moz-appearance: none;
      }

      button:active .button-circle {
        fill: url(#grad-down);
      }

      .clickable-element {
        cursor: pointer;
      }
    `;
  }

  render() {
    const { color } = this;
    return html`
      <button
        aria-label="${color} pushbutton"
        @mousedown=${this.down}
        @mouseup=${this.up}
        @touchstart=${this.down}
        @touchend=${this.up}
        @keydown=${(e: KeyboardEvent) => SPACE_KEYS.includes(e.key) && this.down()}
        @keyup=${(e: KeyboardEvent) => SPACE_KEYS.includes(e.key) && this.up()}
      >
        <svg
          width="18mm"
          height="12mm"
          version="1.1"
          viewBox="-3 0 18 12"
          xmlns="http://www.w3.org/2000/svg"
          xmlns:xlink="http://www.w3.org/1999/xlink"
        >
         [... parts of SVG left out]
        </svg>
      </button>
    `;
  }

  down() {
    if (!this.pressed) {
      this.pressed = true;
      this.dispatchEvent(new Event('button-press'));
    }
  }

  up() {
    if (this.pressed) {
      this.pressed = false;
      this.dispatchEvent(new Event('button-release'));
    }
  }
}

export default PushbuttonElement;

Basically, you can import the pushbutton-element LitElement into your React application like so:

import { useRef, useEffect } from 'react';
import './App.css';
import './litelements/my-element';
import './litelements/pushbutton-element';

function App() {
  const redButtonRef = useRef();
  
  const clickHandler = () => {
    console.log('RED BUTTON PRESSED!');
  }

  useEffect(() => {
    let redButton = null;

    if (redButtonRef.current) {
      redButtonRef.current.addEventListener('button-press', clickHandler);
      redButton = redButtonRef.current;
    }

    return () => {
      if (redButton) {
        redButton.removeEventListener('button-press', clickHandler);
      }  
    };
  });

  return (
    <div className="App">
      <header className="App-header">
        <my-element />
        <br />
        <wokwi-pushbutton color="red" ref={redButtonRef}/>
        <br />
        <wokwi-pushbutton color="green"/>
        <br />
        <wokwi-pushbutton color="yellow"/>
      </header>
    </div>
  );
}

export default App;

You can see that the wokwi-pushbutton element is written in kebab case, instead of the usual Pascal case of React elements. You can pass the values of the properties as usual.

The wokwi-pushbutton emits an event when the button is pressed. The handling of this event is a bit different from the usual React event handling.

Event Handling

For handling the button-press I use a React ref to get the element and add an event listener. Since I use a React function component, everything is within the useEffect function. The event handler simply outputs a message to the console when the event is fired.

Note, that the event handler also needs to deregister with the removeEventListener function.

Conclusion

In this example application you can see that it is possible to integrate LitElements into your React application. For event handling you need to take special care, but is entirely doable.

References

Final example application: https://github.com/tderflinger/react-litelement-example

LitElement: https://lit-element.polymer-project.org/

Create React App: https://create-react-app.dev/

Wokwi LitELement buttons article : https://www.smashingmagazine.com/2020/01/recreating-arduino-pushbutton-svg/

Wokwi LitElements: https://github.com/wokwi/wokwi-elements

Web Components specs: https://www.webcomponents.org/specs

Vaadin Web Components in React: https://vaadin.com/learn/tutorials/using-web-components-in-react

Published 29 Dec 2020

Thomas Derflinger

Thomas Derflinger

I am an independent entrepreneur and software developer.

Web is a topic I really enjoy. Let's get in touch!