Solid Component in React App using Web Components

I wanted to use a Solid element inside a React app. In the end, I was pleasantly surprised how smooth everything went.

This is a quick guide that highlights important steps.

Advantages

  • You can use the same component everywhere, even without frameworks.
  • Output size is very small and doesn't contain a big runtime.
  • All the good stuff Solid brings.

Scope

Using React component in Solid, or having children React components in this Custom Component are hard problems I won't mention.

Resources

It is easier to have some understanding before diving in:
https://developer.mozilla.org/en-US/docs/Web/Web_Components

Best practices:
https://developers.google.com/web/fundamentals/web-components/best-practices
"Aim to only accept rich data (objects, arrays) as properties."

Steps

1- Start with the template
npx degit solidjs/templates/ts my-app

2- Add dependencies
pnpm i solid-element

3- Change vite.config.ts

import { defineConfig } from "vite";
import solidPlugin from "vite-plugin-solid";

const path = require('path')

export default defineConfig({
  plugins: [solidPlugin()],
  build: {
    target: "esnext",
    polyfillDynamicImport: false,
    lib: {
      entry: path.resolve(__dirname, 'src/MyComponent.tsx'),
      name: 'MyLib'
    },
  },
});

4- Create Component MyComponent.tsx

import { onMount, createSignal, createEffect, For } from "solid-js";
import { createStore } from "solid-js/store";

import { customElement } from "solid-element";

const [getData, setData] = createSignal("");

interface Options {
  option1: string;
  option2: number;
}

customElement(
  "my-custom-component",
  {
    data: { getData, setData, getOtherData: null },
  },
  (
    props: {
      data: {
        // flowdata: string;
        getData: () => string;
        setData: (v: string) => string;
        getOtherData: (options: Options) => Promise<string>;
      };
    },
    { element }
  ) => {
    let internal_el;

    props.data.getOtherData = async (
      options: Options = { option1: "default", option2: 1 }
    ): Promise<string> => {
      let promise = new Promise<string>((resolve, reject) => {
        //do something
        resolve("data");
      });
      return promise;
    };

    const [state, setState] = createStore({});

    onMount(() => {
      // code
    });

    createEffect(() => {
      // getData() will be reactive here
      // you can use the passed data to do calculation / render elements
      getData();
    });

    return <div ref={internal_el}></div>;
  }
);

5- Change package.json name field:
"name": "my-custom-component"

6- Run npm run build
Now you can see the result under dist directory. That is all. You can copy my-custom-component.es.js to your React project, or use some multi-repo setup.

7- On the React side of things, you can use methods to exchange data with the Custom Component.

import "../vendor/my-custom-component.es.js";

function Component1(props) {
  const customControlRef = useRef<any>(null);

  useEffect(() => {
    customControlRef.current.data.setData(specialData);
  }, []);

  const getData2 = async (ev) => {
    await customControlRef.current.data.getOtherData();
  };

  return (
    <div>
      <my-custom-component ref={customControlRef}></my-custom-component>

      <button className="button" onClick={getData2}>
        Get some data from Custom Component
      </button>
    </div>
  );
}

8- Bonus: If you are using Typescript add this before the component code in React.

declare global {
  namespace JSX {
    interface IntrinsicElements {
      "my-custom-component": React.DetailedHTMLProps<
        React.HTMLAttributes<HTMLElement>,
        HTMLElement
      >;
    }
  }
}

44