Consuming a Web Component in React in Typescript

React and Web Components

After building my first web component I wanted to use it in my React app that was written in Typescript. I found some blog posts and tutorials about using web components in React apps, but none of those posts/tutorials were Typescript-based, so when I went to add my custom element I got the error [ts] Property 'wc-menu-button' does not exist on type 'JSX.IntrinsicElements'. [2339].

TypeScript Error JSX.IntrinsicElements

The problem is React only knows about standard HTML Elements, so when I go putting my custom element in there, it doesn’t know about it and shows me the error. Obviously Javascript React projects don’t have this error because there are no types (just mayhem and run-time bugs 🙃).

There are 2 ways to fix this. One way is in the web component, the other way is in the React app.

Option 1: Type Definitions in the web component

If you are the author of the web component you can declare your component as part of JSX’s IntrinsicElements, like this:

declare global {
  namespace JSX {
    interface IntrinsicElements {
      'my-element': any;
    }
  }
}

You might think that setting the type of my-element to any is less than ideal, and you’d be right. Wouldn’t it be better to define your attributes in an interface then set your custom element to that type? Something like this:

declare global {
  namespace JSX {
    interface IntrinsicElements {
      'my-element': MyElementAttributes;
    }

    interface MyElementAttributes {
      name: string;
    }
  }
}

This looks well and good, until you try to actually use that custom element in a React project and need to set the key or ref attributes. You’ll be met with an error like [ts] Property 'ref' does not exist on type 'MyElementAttributes'. [2339].

ref property does not exist

The MyElementAttributes needs to extend the React HTMLAttributes class, like this:

interface MyElementAttributes extends HTMLAttributes{
  name: string;
}

But if you try and compile your web component now then tsc (the Typescript compiler) will give you an error because it doesn’t know what the HTMLAttributes type is. You’ll need to take a dev dependency on React types (@types/react). But now your simple web component projects takes a dependency on React types, which might feel a little backward - your standards-based custom element doesn’t need to know about some front-end framework, right? Or maybe it’s fine with you. Either take that dev dependency on react, or you can just say your custom-element is of type any and make sure you have good documentation around the attributes (you should have good documentation anyway, actually).

Or use a tool to help

If you don’t want to deal with generating the IntrinsicElements types yourself, you can use a tool like StencilJS to build your web component. It will take care of types, including a global declare to define your web component inside the IntrinsicElements interface.

Caveat for Stencil - there might currently be a bug with Stencil type generation and React, so until that is fixed you can also put some extra info in your docs about how to handle type declarations in the React app

Or…

Make some good documentation and tell consumers of your web component to declare their own types, which leads us to…

Option 2: Type Definition in the React app

Similar concept to option 1, so I’ll keep this brief. With this options you make the type declaration file in the React app instead of in the web component. I usually just make a declarations.d.ts file and put the type declarations there:

declare namespace JSX {
  interface IntrinsicElements {
    "wc-menu-button": any;
  }
}

Now my React app compiles and consumes my web component.

If you’d like to see a sample in action, I made a sample repo of a React app consuming a web component using option 2. Besides declaring types, this repo also demonstrates how to install, how to set attributes, and how to use ref to set properties of the web component.

React app in Typescript consuming wc-menu-button sample repo

I also made a repo in plain old Javascipt here.

Fixing CSS Tap Effects on iOS Glob Pattern Not Working? Don't Forget the 'Quotes'