Reactifying Custom Elements using a Custom Elements Manifest
Tags: react, customelements, webcomponents, customelementsmanifest
Published: 2021-06-23 13:08:59

---
title: Reactifying Custom Elements using a Custom Elements Manifest
published: true
description: 
tags: react, customelements, webcomponents, customelementsmanifest
//cover_image: https://direct_url_to_image.jpg
---

> 🚨 Since creating the PoC of the plugin in this blogpost, [Burton Smith](https://github.com/break-stuff/) has created a much better version of the plugin demonstrated in this blogpost, it's more feature complete, well maintained, and you should use his plugin instead 🙂 You can find his plugin [here](https://github.com/break-stuff/cem-plugin-react-wrapper).

We finally have a stable version of the Custom Elements Manifest schema, and this means we can finally start creating some cool tooling with it. Don't know what a Custom Elements Manifest is? Read all about it in the [announcement](https://dev.to/open-wc/introducing-custom-elements-manifest-gkk) post.

**TL;DR:** A Custom Elements Manifest is a JSON file that contains all metadata about the custom elements in your project. You can read all about it [here](https://github.com/webcomponents/custom-elements-manifest).

React is a [bit](https://dev.to/bennypowers/what-s-not-new-in-react-18-45c7) of a [pain](https://custom-elements-everywhere.com/#:~:text=LIBRARY-,React%2016.14.0,-SCORE) when it comes to web components, and (unlike other frameworks) requires a bunch of special handling to support HTML. The **TL;DR:** here is that React passes all data to Custom Elements in the form of HTML attributes, and can't listen to DOM events due to reinventing the wheel with their own synthetic events system. 

For fun and science, I decided to try my hand at creating a [@custom-elements-manifest/analyzer](https://www.npmjs.com/package/@custom-elements-manifest/analyzer) plugin to ✨ automagically ✨ create some React wrappers for my custom elements project [generic-components](https://github.com/thepassle/generic-components), which is a collection of accessible, zero-dependency, vanilla JS web components. Do note that this is mostly a PoC, I'm sure things could be improved and edgecases were missed; this is mostly an experiment to see how we can utilize the Custom Elements Manifest. 

In this blog we'll walk through a couple of the steps and decisions to reactify our custom elements, and showcase how we can leverage a projects `custom-elements.json` to achieve this goal.

> You can read more about [@custom-elements-manifest/analyzer](https://www.npmjs.com/package/@custom-elements-manifest/analyzer)s rich plugin system here: [Plugin Authoring Handbook](https://github.com/open-wc/custom-elements-manifest/blob/master/packages/analyzer/docs/plugins.md), and be sure to check out the  [cem-plugin-template]((https://github.com/open-wc/cem-plugin-template)) repository.

If you want to follow along, you can find the code for our `reactify` plugin [here](https://github.com/thepassle/generic-components/blob/master/cem-plugin-reactify.js).

## Custom Elements

First of all, we have to find all the custom elements in our Custom Elements Manifest that we want to reactify. Fortunately, classes in the Manifest that are actually custom elements are flagged with a: `"customElement": true` flag, so we can loop through all the modules of our Manifest, and find any class declaration that has the `customElement` flag:

```js
const elements = [];
customElementsManifest?.modules?.forEach(mod => {
  mod?.declarations?.forEach(dec => {
    if(dec.customElement) elements.push(dec);
  })
});
```

Now that we have an array of all the custom elements in our project, we can start creating some React wrappers.

## Slots

Lets start off easy; slots. Slots are a native way to provide children to your custom elements. Much like React's `children`. Which means... we can use `children` to project any children of the Reactified component, straight to the Custom Element, which (if it supports slots), will correctly render them.

```jsx
function GenericSwitch({children}) {
  return <generic-switch>{children}</generic-switch>
}
```

Usage:
```jsx
<GenericSwitch>Toggle me!</GenericSwitch>
```

Easy peasy.

## Properties

Next up: Properties. In React-land, everything gets passed around as a property. This is forms a bit of a problem, because in HTML-land _not_ everything is a property, we also have _attributes_. Sometimes, an elements attributes and properties are even synced up, and this could mean that there are attributes and properties with the same name; like an element with a `disabled` attribute/property or a `checked` attribute/property.

Fortunately, in a Custom Elements Manifest we can make a distinction between the two. If an attribute has a relation with a corresponding property, it will have a `fieldName` property:

```json
  "attributes": [
    {
      "name": "checked",
      "type": {
        "text": "boolean"
      },
      "fieldName": "checked"
    },
  ]
```

This means that we can ignore the `checked` _attribute_, but interface with the `checked` _property_ instead, and avoid having two props with the same name.

Because React will set everything on a custom element as an attribute (ugh), we have to get a `ref` for our custom element, and set the property that way. Here's an example:

```jsx
function GenericSwitch({checked}) {
  const ref = useRef(null);

  useEffect(() => {
    ref.current.checked = checked;
  }, [checked]);

  return <generic-switch ref={ref}></generic-switch>
}
```


## Attributes

This is where things get a little bit more interesting. Again, in React-land, everything gets passed around as a property. However, it could be the case that a custom element has an _attribute name_ that is a reserved keyword in JS-land. Here's an example:

```html
<generic-skiplink for="someID"></generic-skiplink>
```

In HTML, this `for` attribute is no problem. But since we're reactifying, and everything in React-land gets passed around as a JavaScript property, we now have a problem. Can you spot what the problem is in this code?

```jsx
function GenericSkiplink({for}) {
  return <generic-skiplink for={for}></generic-skiplink>
}
```

Exactly. `for` is a reserved JavaScript keyword, so this will cause an error. In order to avoid this, we'll provide an attribute mapping to avoid these kinds of clashes:

```js
export default {
  plugins: [
    reactify({
      // Provide an attribute mapping to avoid clashing with React or JS reserved keywords
      attributeMapping: {
        for: '_for',
      },
    }),
  ],
};
```

Whenever we find an attribute that is a reserved keyword in JavaScript, we try to see if there was an attributeMapping for this attribute provided, and if not; we have to throw an error. Using this attributeMapping, the resulting React component now looks like:

```jsx
function GenericSkiplink({_for}) {
  return <generic-skiplink for={_for}></generic-skiplink>
}
```

Note that we don't want to change the actual attribute _name_, because that would cause problems, we only change the _value_ that gets passed to the attribute.


### Boolean attributes

Boolean attributes require some special attention here, as well. The way boolean attributes work in HTML is that the presence of them considers them as being true, and the absence of them considers them as being false. Consider the following examples:

```html
<button disabled></button>
<button disabled=""></button>
<button disabled="true"></button>
<button disabled="false"></button> <!-- Yes, even this is considered as `true`! -->
```

Calling `button.hasAttribute('disabled')` on any of these will result in `true`.

This means that for boolean attributes, we can't handle them the same way as regular attributes by only calling `ref.current.setAttribute()`, but we need some special handling. Fortunately, the Custom Elements Manifest supports types, so we can easily make a distinction between 'regular' attributes, and boolean attributes:

```diff
  "attributes": [
    {
      "name": "checked",
      "type": {
+       "text": "boolean"
      },
      "fieldName": "checked"
    },
  ]
```

## Events

React has their own synthetic event system to handle events, which doesn't play nice with custom elements (read: HTML). Fortunately, we can easily reactify them. React events work with the following convention:

```jsx
<button onClick={e => console.log(e)}/>
```

Our Custom Elements Manifest very conveniently holds an array of Events for our custom elements:

```json
  "events": [
    {
      "name": "checked-changed",
      "type": {
        "text": "CustomEvent"
      }
    }
  ],
```

This means we can find all events for our custom element, prefix them with `on`, and capitalize, and camelize them; `onCheckedChanged`.

Then we can use our `ref` to add an event listener:
```jsx
function GenericSwitch({onCheckedChanged}) {
  const ref = useRef(null);

  useEffect(() => {
    ref.current.addEventListener("checked-changed", onCheckedChanged);
  }, []);

  return <generic-switch ref={ref}></generic-switch>
}
```

## Importing

Finally, we need to create the import for the actual custom element in our reactified component. Fortunately for us, if a module contains a `customElements.define()` call, it will be present in the Manifest. This means we can loop through the Manifest, find where our custom element gets defined, and stitch together some information from the `package.json` to create a bare module specifier:

`switch.js`:
```js
import { GenericSwitch } from './generic-switch/GenericSwitch.js';
customElements.define('generic-switch', GenericSwitch);
```

Will result in:
`custom-elements.json`:
```json
{
  "kind": "javascript-module",
  "path": "switch.js",
  "declarations": [],
  "exports": [
    {
      "kind": "custom-element-definition",
      "name": "generic-switch",
      "declaration": {
        "name": "GenericSwitch",
        "module": "/generic-switch/GenericSwitch.js"
      }
    }
  ]
},
```

By stitching together the `name` property from the projects `package.json`, and the `path` from the module containing the custom element definition, we can construct a bare module specifier for the import:

```js
import '@generic-components/components/switch.js';
```

## Using the plugin

To use our [@custom-elements-manifest/analyzer](https://www.npmjs.com/package/@custom-elements-manifest/analyzer) Reactify plugin, all I have to do is create a `custom-elements-manifest.config.js` in the root of my project, import the plugin, and add it to the `plugins` array:

`custom-elements-manifest.config.js`:
```js
import reactify from './cem-plugin-reactify.js';

export default {
  plugins: [
    reactify()
  ]
};
```

This means that every time I analyze my project, it will automagically create the Reactified wrappers of my custom elements:

```
└── legacy
    ├── GenericAccordion.jsx
    ├── GenericAlert.jsx
    ├── GenericDialog.jsx
    ├── GenericDisclosure.jsx
    ├── GenericListbox.jsx
    ├── GenericRadio.jsx
    ├── GenericSkiplink.jsx
    ├── GenericSwitch.jsx
    ├── GenericTabs.jsx
    └── GenericVisuallyHidden.jsx
```

## Result

And as a final result, here's our reactified Custom Element that correctly handles:
- Events
- Properties
- Attributes
- Boolean attributes
- Slots

```jsx
<GenericSwitch
  disabled={false} // boolean attribute
  checked={true} // property
  label={'foo'} // regular attribute
  onCheckedChanged={e => console.log(e)} // event
>
  Toggle me! // slot
</GenericSwitch>
```

{% stackblitz react-kghcjw %}

## Concluding

While it's cool that we finally have a stable version of the Custom Elements Manifest, which allows us to automate things like this, working on this reactify plugin made me realize how backwards it even is that we need to resort to shenanigans like this, and I hope React will seriously consider supporting HTML properly in future versions.