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
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'
},
},
});
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
>;
}
}
}
Top comments (4)
Wouldn't it be easier to have a
useSolid(component, props, ref)
hook that rendered the solid component in the node referenced by theref
?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.
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.
Sure, let me know and I will also add a link to your solution in the post.