Solid Component in React App using Web Components
Tags: solidjs, vite, javascript, react
Published: 2021-07-10 16:51:39


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.

![End Result](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5gnsehfyh19c021o80vq.png)

## Resources
solid-element library:
https://github.com/solidjs/solid/tree/main/packages/solid-element

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`
```typescript
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`
```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.
```tsx

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.
```tsx
declare global {
  namespace JSX {
    interface IntrinsicElements {
      "my-custom-component": React.DetailedHTMLProps<
        React.HTMLAttributes<HTMLElement>,
        HTMLElement
      >;
    }
  }
}
```