Cory Rylan

My name is , Google Developer Expert, Speaker, Software Developer. Building Design Systems and Web Components.

Follow @coryrylan
React JS

How to use Web Components with TypeScript and React

Cory Rylan

- 3 minutes

In our previous post, we learned how to use Web Components in React. React recently added experimental support of Web Components enabling our React apps to leverage the large Web Component ecosystem. We can also use Web Components in TSX/TypeScript templates providing type checking in our components.

We will use the same alert Web Component from the previous tutorial in our example.

A Web Component working in React

To use the alert in our React component, we import the component and add the x-alert tag.

import React, { useState }  from 'react';
import './alert.js';

export default function App() {
  const [show, setShow] = useState(true);

  return (
    <div>
      <button onClick={() => setShow(!show)}>toggle alert</button>

      <x-alert hidden={show} status="success" closable oncloseChange={() => setShow(!show)}>
        This is a Web Component in React
      </x-alert>
    </div>
  );
}

With the recent support for Custom Elements, we can now set properties and listen to events on our Web Component elements. If we are using TypeScript, however, we will likely see some errors in our TSX files like this below:

Unknown Web Component in TSX file

At runtime, React will work correctly with our Web Component. However, TypeScript cannot type check our TSX template due to it not knowing how to find the CustomElement class reference for our associated tag. We can add our tag to the JSX.IntrinsicElements global interface, which will associate x-alert to the XAlert class reference.

import React, { useState, DOMAttributes }  from 'react';
import { XAlert } from './alert.js';
import './alert.js';

type CustomElement<T> = Partial<T & DOMAttributes<T> & { children: any }>;

declare global {
  namespace JSX {
    interface IntrinsicElements {
      ['x-alert']: CustomElement<XAlert>;
    }
  }
}

Using TypeScript Generics, we can pass and combine our Custom Element class XClass to the React DOMAttributes. This will add the React event types like onClick to our element. Now, if we look at our element, we should see the following,

Web Component CustomEvents in TSX and React

Our component does not recognize the closeChange event that our Web Component emits when the user clicks the close button. However, we can leverage TypeScript Template Literal Types to define what events are available easily.

type CustomEvents<K extends string> = { [key in K] : (event: CustomEvent) => void };

type CustomElement<T, K extends string> = Partial<T & DOMAttributes<T> & { children: any } & CustomEvents<`on${K}`>>;

declare global {
  namespace JSX {
    interface IntrinsicElements {
      ['x-alert']: CustomElement<XAlert, 'closeChange' | 'openChange'>;
    }
  }
}

Using Template Literal Types, we can create a list of string types and generate a new list of string types with the on prefix required for React. Then we can take pass our event strings to the CustomEvents type, which will add the function signature for us. So now we have type checking on our custom events for our Web Components in React and TSX.

import React, { useState, DOMAttributes }  from 'react';
import { XAlert } from './alert.js';
import './alert.js';

type CustomEvents<K extends string> = { [key in K] : (event: CustomEvent) => void };

type CustomElement<T, K extends string> = Partial<T & DOMAttributes<T> & { children: any } & CustomEvents<`on${K}`>>;

declare global {
  namespace JSX {
    interface IntrinsicElements {
      ['x-alert']: CustomElement<XAlert, 'closeChange' | 'openChange'>;
    }
  }
}

export default function App() {
  const [show, setShow] = useState(true);

  return (
    <div>
      <button onClick={() => setShow(!show)}>toggle alert</button>

      <x-alert hidden={show} status={'success'} closable oncloseChange={() => setShow(!show)}>
        This is a Web Component in React
      </x-alert>
    </div>
  );
}

With a bit of TypeScript Generics and Template Literal Types magic, we can create an excellent developer experience using TypeScript, React, and Web Components.

If you maintain a Web Component library check out my library custom-element-types which will generate type definitions for various frameworks as described in this blog post automatically. Otherwise check out the demo below!

View Demo Code   
Twitter Facebook LinkedIn Email
 

No spam. Short occasional updates on Web Development articles, videos, and new courses in your inbox.

Related Posts

Web Components

Reusable Component Patterns - Default Slots

Learn about how to use default slots in Web Components for a more flexible API design.

Read Article
Web Components

Reusable Component Anti-Patterns - Semantic Obfuscation

Learn about UI Component API design and one of the common anti-patterns, Semantic Obfuscation.

Read Article
React JS

How to use Web Components in React

Learn how to use Web Components in React with properties and custom events.

Read Article