DEV Community

Cover image for Solid Component in React App using Web Components
Fatih Pense
Fatih Pense

Posted on

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.

End Result

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

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'
    },
  },
});
Enter fullscreen mode Exit fullscreen mode

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>;
  }
);


Enter fullscreen mode Exit fullscreen mode

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>
  );
}

Enter fullscreen mode Exit fullscreen mode

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
      >;
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Top comments (4)

Collapse
 
lexlohr profile image
Alex Lohr

Wouldn't it be easier to have a useSolid(component, props, ref) hook that rendered the solid component in the node referenced by the ref?

Collapse
 
fatihpense profile image
Fatih Pense

Hi Alex,
Do you mean, inside the React app? Then, I think Solid will recompile for each render.

Also, using Solid and React (different kinds of JSX) in the same project can require some complicated setup. I went with two common projects and a clear standard API in between. There are limitations Web Components bring, but capabilities are very clear.

However, your proposal might be better for some use cases. I'm not an expert on this topic.

Collapse
 
lexlohr profile image
Alex Lohr

I meant to use a useEffect for the initial render and another one for props updates to avoid that exact issue.

And I obviously wanted to use an already compiled solid component, otherwise I would have proposed the component with props as JSX call instead of separating component and props.

If I find the time, I'll try it and link to this article for reference.

Thread Thread
 
fatihpense profile image
Fatih Pense

Sure, let me know and I will also add a link to your solution in the post.