Important: This documentation covers Yarn 1 (Classic).
For Yarn 2+ docs and migration guide, see yarnpkg.com.

Package detail

react-to-html-element

bougassaa183ISC1.4.7

Converts React Components to HTML Element (also known as Web Component, Custom Element)

react to web component, react to webcomponent, react to html, react to custom element, react html, react component html, react component web component, react component webcomponent, react component custom element, convert react, convert react to webcomponent, WebComponent, WebComponents, Web Component, HTMLElement, HTMLElements, Custom elements, React, ReactDOM, HTML

readme

Node.js CI made-with-javascript Maintenance GitHub latest commit Npm package version

react-to-html-element

react-to-html-element turns a React component into a Web Component. To see a practical case of how to use in your existing applications, see the Example section (after reading the documentation of course 😊)

Sections :

Usages

The React component must declare its properties and their types in the static componentProps attribute (or prop-types), as in the example below.

import React from 'react';
import PropTypes from "prop-types"; // add this line, if you want to declare with PropTypes

const MyButton = ({ someBool, someNumber, someString, someObject, someArray, someSlot, children }) => {
    // do anything here with properties
    return (
        <button disabled={someBool} data-identifier={someNumber} data-extra={someString}>
            {children}
        </button>
    );
}

// Declare as below without using prop-types
MyButton.componentProps = {
    someBool: Boolean,
    someNumber: Number,
    someString: String,
    someObject: Object,
    someArray: Array,
    someSlot: Node,
    someFunc: Function,
}

// Or else using prop-types 
MyButton.propTypes = {
    someBool: PropTypes.bool,
    someNumber: PropTypes.number,
    someString: PropTypes.string,
    someObject: PropTypes.object,
    someArray: PropTypes.array,
    someSlot: PropTypes.node,
    someFunc: PropTypes.func,
}

export default MyButton;

Then after declaring the properties, you must register your React components as WebComponent, like this :

import React from 'react';
import * as ReactDOM from "react-dom/client";
import { register } from "react-to-html-element";
import MyButton from "./src/MyButton";

register(MyButton, 'my-button', React, ReactDOM);

It's up to you to find the ideal location to register your components and then build them, export them, publish them etc... (you can imagine publishing them to a CDN, npm...)

Use of the web component created :

<html>
<body>
   <my-button some-bool="true" some-number="45" some-string="Hello" some-object='{"name": "Will"}' some-array="[1, 2, 3]">
       It's a Button
   </my-button>
</body>
</html>

Extend the WebComponent

You can create an extension of the WebComponent to suit your needs, by adding {returnElement: true} as options. and then it's up to you to define the WebComponent in the DOM.

// ...
import { register } from "react-to-html-element";
import MyButton from "./src/MyButton";

class WCButton extends register(MyButton, null, React, ReactDOM, {returnElement: true})
{
    constructor(props) {
        super(props);
    }

    connectedCallback() {
        super.connectedCallback();
        console.log('Component connected to the DOM');
    }
}

customElements.define('my-button', WCButton); // define your component to the DOM

Slots

It is possible to add slots in your custom elements. They will be added to the React component props. Here is an example of how to do it :

<my-dialog>
    <slot name="header">...</slot>
    <slot name="body">...</slot>
    <slot name="footer">...</slot>
</my-dialog>
<!-- Or else you can do as below -->
<my-dialog>
    <h3 slot="header">...</h3>
    <p slot="body">...</p>
    <div slot="footer">...</div>
</my-dialog>

In your React component :

import React from 'react';

const MyDialog = ({ header, body, footer }) => {
    return (
        <div>
            <div>{header}</div>
            <div>{body}</div>
            <div>{footer}</div>
        </div>
    );
}

MyDialog.componentProps = {
    header: Node,
    body: Node,
    footer: Node,
}

export default MyDialog;

rootElement

This is a property injected into all registered React components, which corresponds to the instance of the WebComponent rootElement instanceof HTMLElement. For example, it can be used to trigger or intercept JavaScript events :

function MyButton({ someString, rootElement }) {
    const buttonClicked = () => {
        const event = new CustomEvent('btnClicked', { detail: {identifier: 45}})
        rootElement.dispatchEvent(event)
    }

    return (
        <button onClick={buttonClicked}>{someString}</button>
    );
}

How to listen event :

<html>
<body>
    <my-button some-string="Hello"></my-button>

    <script>
        document.addEventListener('DOMContentLoaded', function () {
            let button = document.querySelector('my-button');

            button.addEventListener('btnClicked', function (e) {
                console.log('identifier clicked : ' + e.detail.identifier);
            });
        })
    </script>
</body>
</html>

Usage of Ref

Let's take the example of a React component that has a function that can be called from the DOM, for example validating that an input is valid and returning true if it is or otherwise false.

import React, {forwardRef, useImperativeHandle} from 'react';

const MyInput = forwardRef(({placeholder}, ref) => {

    useImperativeHandle(ref, () => ({
        isValid: () => {
            // put logic here
            return true; // or return false
        }
    }));

    return <input placeholder={placeholder} type="text"/>;
});

MyInput.componentProps = {
    placeholder: String,
}

export default MyInput;

There are 3 important points :

  • The component must be wrapped with forwardRef hook.
  • Add as second parameter the variable ref, example (props, ref) or ({props1, prop2}, ref).
  • Use the useImperativeHandle hook to expose functions outside the component.

After doing that, now here is an example of how to register the component :

// ...
import { register } from "react-to-html-element";
import MyInput from "./src/MyInput";

class WCInput extends register(MyInput, null, React, ReactDOM, {returnElement: true, hasReactRef: true})
{
    constructor(props) {
        super(props);
    }

    async isInputValidAsync() {
        let ref = await this.getAsyncReactRef();
        return ref.isValid(); // call the React component function
    }

    isInputValid() { // or you can do it like this
        return this.getReactRef().isValid(); // call the React component function
    }
}

customElements.define('my-input', WCInput); // define your component to the DOM

// call function like this :
let input = document.querySelector('my-input');
let isValid = input.isInputValid();

// or with async (inside async function) :
let input = document.querySelector('my-input');
let isValid = await input.isInputValidAsync();

The asynchronous should be used in case the WebComponent may not be ready in the DOM yet, to avoid having undefined

Below is another example of using the ref to sibling the input inside a React component and toggle focus on it :

// ...
const MyInput = forwardRef((props, ref) => {
    return <input ref={ref} type="text"/>;
});

class WCInput extends register(MyInput, null, React, ReactDOM, {returnElement: true, hasReactRef: true})
{
    // ...
    focusInput() {
        this.getReactRef().focus(); // call focus method of input
    }
}

// clicking on a button activates the focus on the input :
let input = document.querySelector('my-input');

button.addEventListener('click', function () {
    input.focusInput();
});

Update attributes

After the component has been rendered, you can update the attributes, and the component will be re-rendered :

let button = document.querySelector('my-button');
button.someString = "Good bye";
// or
button.setAttribute("some-string", "Good bye");

button.someArray = [1, 2, 3];
button.someBool = true;
// ...

Function attributes need a reference to declared function:


<my-button handle-click="sayHello"></my-button>
<my-button handle-click="Greeting.sayHello"></my-button>

<script>
  function sayHello() { ... }

  class Greeting {
    static sayHello() { ... }
  }
</script>

// and retrieve the function inside you React component (props.handleClick)

API

The register function has as parameters :

  • ReactComponent The React component that needs to be turned into a WebComponent.
  • name The name of the desired WebComponent tag.
  • React The version of React that was used to create the components.
  • ReactDOM The version of ReactDOM that was used to create the components.
  • options : object of options default = {modeShadow: false, returnElement: false, hasReactRef: false, className: "html-element"}
    • modeShadow Create components in shadow mode.
    • returnElement The function returns the WebComponent to be overridden
    • hasReactRef The React component will have ref functionality enabled
    • className CSS class that will be added to the HTML container

Conflicts to avoid

There are points to know to allow the proper functioning of this package:

  • If you rewrite the connectedCallback method of the returned component always call the parent by doing: super.connectedCallback();
  • The children, rootElement and ref properties do not need to be declared, they will be automatically injected into the component.
  • Do not use these properties in your components: custom custom-parent custom-state
  • If you encounter a problem of FOUC, flickering, glitch (visual problem), add these CSS rules: `css [custom]:not([custom-state="hydrated"]) { visibility: hidden; }

:not(:defined) { visibility: hidden; }


## Example
See example on codesandbox: [https://codesandbox.io/s/react-to-html-element-397stp](https://codesandbox.io/s/react-to-html-element-397stp)

To go further, you can imagine for example having a component library in React, which you could export to all your applications, here is an example among hundreds that you could imagine.

Start by creating a React app using [create-react-app](https://reactjs.org/docs/create-a-new-react-app.html). You can base yourself on the example in [codesandbox](https://codesandbox.io/s/react-to-html-element-397stp), it's exactly the same things except that we will talk about the build and include in your applications.

npx create-react-app my-web-components cd my-web-components

You can remove all the files created by create-react-app inside the `src` folder, it's up to you to create your tree (structure). You can imagine something like this :

my-web-components/ └── src/ ├── components/ │ ├── MyButton.jsx │ ├── MyInput.jsx │ └── ... ├── layouts/ │ ├── Header.jsx │ ├── Footer.jsx │ └── ... ├── style/ │ ├── index.css │ ├── my-button.css │ ├── my-input.css │ └── ... └── index.js package.json


Install `react-to-html-element` in your project:

npm install react-to-html-element


Then it's up to you, from the README and the examples in codesandbox, create your components and **register** them in the `src/index.js` file!

Then we will use the react-scripts build command which is already installed with create-react-app, being at the root of your project and doing this command :

npm run build


After the command completes, there is a `build` folder created in the root. Inside are the compiled and minified files, for example :

build/static/js/main.e77e15c3.js build/static/css/main.2e73bf20.css 👈 and if you included css


These files contain your WebComponents ready to be used anywhere! How to use them :

```html
<!doctype html>
<html>
<head>
    <link rel="stylesheet" href="build/static/css/main.2e73bf20.css">
</head>
<body>
    <my-button>Button</my-button>

    <script src="build/static/js/main.e77e15c3.js"></script> <-- put the JS build here
</body>
</html>