Integrate React component into Solid JS (with Typescript) (part 1)
TuanNQ

TuanNQ @tuannq

Joined:
May 19, 2025

Integrate React component into Solid JS (with Typescript) (part 1)

Publish Date: Jun 7
0 0

I really like Solid JS, but...

...I can't live without libraries like react-three-fiber and react-flow. There are currently no alternative to those library in the Vanilla Javascript world. Solid is amazing: having both velocity (easy to code) and performance seems like a dream come true, but I can't leave React ecosystem (one of the largest ecosystem) behind. Writing all of the diffuse light and blur shadow in react-three-fiber, or recreate react-flow is insane. I'm not going to do that! I don't want to write 100.000 lines in Solid JS just to finally have to convert to React again!

I can't leave React ecosystem

That's why I write this article: how to integrate React component into Solid JS.

In part 1, we'll learn how to render a React component in Solid JS through simple minimal example like this:

minimal render react component in solid

You can check the source code here on Github.

In part 2, we'll learn how passing data between React component and Solid JS through this counter application:

minimal integrate react component in solid

You can check the source code here on Github.

In part 3 we'll learn how to build and config a more (somewhat) "real-world" application:

cool 3D application with 3D shirt render in a Solid project

You can view this application here and check the source code here on Github.

There are many ways to integrate React component into Solid JS, here I'll introduce the 'monorepo' way of doing it.

Why it seems like an easy-to-solve problem but actually not

At first, integrate React into Solid JS seems like it's not going to be a problem at all. React and Solid is so similar. Can't we just install both React and Solid JS into one project, and then write parent component in Solid JS, the child component in React, and that's it?

Naive React component in Solid component implementation
Naive React component in Solid component implementation

It's not that simple.

Turns out the Vanilla JS that React code and Solid code are compiled down to are completely different (even the compiled JSX part is different!). The compiled React code is not "compatible" with the compiled Solid code. And we also have to tell Vite to compile React and Solid files differently.

Naive React component in Solid component implementation problems

Basically we have 2 big problems:

  • How do we even tell Vite / CRA to compile React and Solid differently?
  • How do React code and Solid code communicate with each other?

Compiling issue

Communication issue

My approach to the compile problem is to create 2 different project, one for the React component and one for the Solid main app.

Solution to the compile problem: separate React and Solid project
Solution to the compile problem: separate React and Solid project

For the communication problem my solution is to compile the React component into Vanilla JS and then install it into the main Solid app.

React and Solid can't understand each other but they both understand a common language: Vanilla JS. By compile the React component into Vanilla JS, Solid will be able to understand it.

Solution to the communication problem: compile React into Vanilla JS
Solution to the communication problem: compile React into Vanilla JS

Now that they understand each other, the Solid main application will give the React component a div to render on.

Solid give react component a div

Here's a more technical diagram:

React component render on a div

  • The React component library expose a mount function: give it a div, the mount function will render on that div
  • Compile it into Vanilla JS
  • Import it into the Solid application
  • Since we compile the React code into Vanilla JS, the Solid application will understand
  • Supply a div to the mount function and tell it to render

So let's get into how to make the React component into a separate library with the help of PNPM Workspace.

Solving the compile problem (with PNPM workspace)

We'll create 2 separate applications: React application and Solid application. Imagine the Solid application is the "main" application while the React application is a "library" that you npm install into the Solid "main" application.

Here's the traditional workflow:

  • Develop and build the React app
  • Publish to NPM Registry
  • Install it from NPM Registry to the Solid app

Traditional workflow

Looking at this workflow you might think, what's the point of making and publishing a library that only I use? I wish I don't have to publish to NPM Registry. Is there someway to use a package locally in my computer?

This is when PNPM workspace come to the rescue.

PNPM workspace

PNPM workspace helps one project use another project as a library without having to publish to NPM Registry.

Let's take an example: project #1 uses project #2 and project #3 as dependencies:

pnpm workspace example

In our example, here's what PNPM Workspace would do:

  • Project #1 asks PNPM Workspace for Project #2 and Project #3 as dependencies
  • PNPM Workspace finds the location of Project #2 and Project #3 (through pnpm-workspace.yaml)
  • PNPM Workspace then asks for the build files of Project #2 and Project #3 (through package.json of each project)
  • PNPM Workspace deliver those files to Project #1

pnpm workpsace project 1 asks for project 2 and 3

pnpm workspace call project 2 and 3

pnpm workspace get dist folder from project 2 and 3 to project 1

Here's a more... technical diagram explains the basic file structure of our example PNPM Workspace:

pnpm workspace folder structure detail

  • pnpm-workspace.yaml at the root of project: tells pnpm workspace the location of all projects
  • packages is the folder houses all projects: if you rename this folder you have to change in the pnpm-workspace.yaml accordingly
  • package.json in the "library" (in our example, project #2 and #3): show the entry to that library
  • package.json in "parent" project: the "workspace:*" part tells pnpm workspace that it wants to use project #2 and project #3 locally

Yarn workspace and NPM workspace also offer the same functionality but in this article I'll use PNPM workspace. They're all really similar to each other.

So let's use PNPM workspace to build a Solid project that uses a React component.

react in solid monorepo

Let's create a PNPM Workspace and add our Solid project to it

First, let's create a new folder for our new project. I'll name it react-in-solid.

Next, create the pnpm-workspace.yaml file tell PNPM about our main Solid project called "solid-project":

packages:
  - "packages/solid-project"
Enter fullscreen mode Exit fullscreen mode

Then, create the packages folder that will house both our React and Solid project. In that folder, let's use Vite to create a new Solid project named "solid-project".

cd packages

npx create-vite@latest

√ Project name: ... solid-project
√ Select a framework: » Solid
√ Select a variant: » TypeScript
Enter fullscreen mode Exit fullscreen mode

Here's a diagram explain what we did:

Image description

Now let's install dependencies. Remember to run install at the root folder. PNPM needs to know about pnpm-workspace.yaml.

pnpm install
Enter fullscreen mode Exit fullscreen mode

Image description

Next, let's run the server by:

pnpm run dev
Enter fullscreen mode Exit fullscreen mode

Remember to run this in the packages/solid-project folder!

Optional: the --filter flag

Having to remember to switch folder to run certain command is quite annoying sometimes. So PNPM workspace offers the --filter flag to solve this problem.

You can run the start dev server above command at the root project with the --filter flag like this:

pnpm --filter \"solid-project\" run dev
Enter fullscreen mode Exit fullscreen mode

The --filter flag specify which project that the dev command should run. It's like you run pnpm run dev at the "solid-project" folder.

Image description

For our convenience, let's make a package.json file at the root of the project and then add the script dev. First, let's run:

pnpm init
Enter fullscreen mode Exit fullscreen mode

Then, in the package.json file add the "dev" script:

"scripts": {
    "dev": "pnpm --filter \"solid-project\" run dev"
}
Enter fullscreen mode Exit fullscreen mode

Now you can run pnpm run dev in the root folder!

Let's create our React project and add to PNPM workspace

First, add our new project called react-component into pnpm-workspace.yaml to tell pnpm about it:

packages:
  - "packages/solid-project"
  - "packages/react-component"
Enter fullscreen mode Exit fullscreen mode

Then, use Vite to create a new React project in the packages folder.

cd packages

npx create-vite@latest

√ Project name: ... react-component
√ Select a framework: » React
√ Select a variant: » TypeScript + SWC
Enter fullscreen mode Exit fullscreen mode

I use SWC here for speed but you can just choose Typescript and leave out SWC if you like

Next, install dependencies from the root folder:

pnpm install
Enter fullscreen mode Exit fullscreen mode

Here's a diagram explain what we did:

Image description

Linking the React and Solid project together

Before continuing how to render a React component in a Solid project, you might want to get a rough idea about what Vite, Solid or React does.

Optional: what does Vite and React (and Solid) do?

In simple term, React (and Solid) allows us to "draw" our interface using Javascript on a div.

Image description

  • You (the developers) write code to teach React (or Solid JS) how to create the interface we want
  • We then supply an index.html with a root div
  • React draw on the root div

So with the bird's eye overview of what React does, here's what the setup code in the main.tsx means:

Image description

We supply the createRoot function with div with id "root", and then tell React (or Solid) to insert elements on that div based on the JSX code we write.

Since the browser only understand Vanilla JS, Vite then compile and bundle the React source code and our code into Vanilla JS for us, supply the index.html file with the "#root" div, and create the dev-server so that we can run in http://localhost:5173.

Image description

Overall architecture

Back to our initial idea of how to render React component in a Solid app:

Image description

Combine with the basic understand of what React and Vite do for us behind the scene, here's the complete architecture:

Complete architecture

  • Our own React component library expose a mount function: give it a div, it'll render on that div
  • Compile our own component library into Vanilla JS
  • Import it into the Solid application
  • Supply a div to the mount function and tell it to draw
  • Vite will do the rest of the compiling, bundling, hot reloading,... for us

Let's apply it to build a Hello World "React in Solid application"!

Hello World React in Solid application

Compile the React component (or we can call it make it a library!)

First, in the React app, let's delete everything in the ./src directory. Then, create the App.tsx file in that directory with the following content:

// src/App.tsx
export const App = () => {
  return <div>Hi from React</div>;
};
Enter fullscreen mode Exit fullscreen mode

The App component render the div with the text "Hi from React" inside.

Next, create the index.tsx file with the following content:

// src/index.tsx
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import { App } from "./App";

// For the main Solid app
export const mount = (root: HTMLElement) => {
  createRoot(root).render(
    <StrictMode>
      <App />
    </StrictMode>
  );
};
Enter fullscreen mode Exit fullscreen mode

The mount function accept a root HTML element and render the React element on it. The index.tsx file serves as an entry point for the Solid app.

Now, let's compile the React code we write above to Vanilla JS for the main Solid app to use.

One of the most simple way to compile React TSX code into Vanilla JS is to use tsc. tsc (installed with Typescript) will compile .ts, .tsx file into Vanilla Javascript. You can use tools from Webpack (which CRA use), ESBuild (which Vite use), Rspack (which Rsbuild use), to tools that built specifically for compiling library like Tsup, Rslib, to very basic simple tool like tsc, swc. In this article for the sake of simplicity I'll use tsc.

Let's create tsconfig.build.json file specifically for tsc to compile our React app. We'll tell tsc to start with an entry file ./src/index.tsx and output the compiled Vanilla JS into the dist folder like so:

{
  "compilerOptions": {
    "target": "ES2016",
    "jsx": "react-jsx",
    "module": "Preserve",
    "declaration": true,
    "inlineSourceMap": true,
    "outDir": "./dist",
    "esModuleInterop": true,
    "forceConsistentCasingInFileNames": true,
    "strict": true,
    "skipLibCheck": true
  },
  "files": ["./src/index.tsx"]
}
Enter fullscreen mode Exit fullscreen mode

Next, we'll tell tsc to use the tsconfig.build.json when compile our React app. In the package.json of the react-component project, change the build command to this:

"scripts": {
    ...
    "build": "tsc -p tsconfig.build.json",
    ...
  },
Enter fullscreen mode Exit fullscreen mode

Now, in the react-component project, let's run build our react component:

pnpm run build
Enter fullscreen mode Exit fullscreen mode

You'll see it generate a new dist folder with all the compiled Vanilla JS file with all the .d.ts type file necessary for it.

The last step we need to do is to set the entry file for our React library. In this case, the entry is the compiled index.tsx file: ./dist/index.js. In the package.json file, add to following:

{
  "name": "react-component",
  "main": "./dist/index.js",
  ...
}
Enter fullscreen mode Exit fullscreen mode

Here's a diagram explain what we've done so far:

Image description

Optional: Running the react-component project independently

Out of all what Vite does for us, to make the React component a library we only need the compile phase.

Image description

This means that we can uninstall Vite and only keep the part that compile for us (which behind the scene is ESBuild / SWC). Though it's ok to do that, I want to keep Vite around so that we can have both options to run this project independently and serves as a library.

The tricky thing is while the main Solid application only expects the React component to return a mount function but not to run it immediately, Vite expects the React component to run it immediately. You can see that in the default main.tsx given to us will render our app in the #root div immediately:

createRoot(document.getElementById('root')!).render(
  <App/>,
)
Enter fullscreen mode Exit fullscreen mode

Image description

To solve this problem, we'll create 2 entry file. The first is index.tsx which export a mount function for the main Solid application to use. The second is main.tsx which actually run the mount function on the #root div.

Image description

Here's a more technical diagram:

Image description

Let's create the main.tsx file that serves as an entry point for stand-alone development with the following:

// src/main.tsx
import { mount } from ".";

// For stand-alone development
mount(document.getElementById("root")!);
Enter fullscreen mode Exit fullscreen mode

This main.tsx file will immediately call the mount function to render our React component in the #root div.

In index.html file of the React component application, make sure the tag import main.tsx file:

...
  <script type="module" src="/src/main.tsx"></script>
...
Enter fullscreen mode Exit fullscreen mode

The index.html file will import main.tsx file (make sure it's not the index.tsx file) which will run the React application for us.

If you run the server in react-component, you'll this on the screen:

Image description

Import into Solid application

First, let's "install" our React application as a dependency. In the package.json of the solid-project, add the following:

"dependencies": {
    ...
    "react-component": "workspace:*"
  },
Enter fullscreen mode Exit fullscreen mode

Here's a diagram explain what we've done so far:

Image description

Now let's run pnpm install react component into the Solid main application. Remember to run at the root folder:

pnpm install
Enter fullscreen mode Exit fullscreen mode

Next, in the solid-project app, let's delete everything in the src folder. First, create the App.tsx with the following content:

export const App = () => {
  return <div>Hi from Solid</div>;
};
Enter fullscreen mode Exit fullscreen mode

The App component render a div with text "Hi from Solid".

Next, create the index.tsx with the following content:

import { render } from 'solid-js/web'
import { App } from './App.tsx'

const root = document.getElementById('root')

render(() => <App />, root!)
Enter fullscreen mode Exit fullscreen mode

The index.tsx render our app in the div with id "root".

If you run the server, you'll see the following in the screen:

Image description

Next, in App.tsx, let's import the React application into our main Solid application:

import { mount } from "react-component";
import { onMount } from "solid-js";

export const App = () => {
  let container!: HTMLDivElement;

  onMount(() => {
    mount(container);
  });

  return (
    <>
      <div ref={container} />
      <div>Hi from Solid</div>
    </>
  );
};
Enter fullscreen mode Exit fullscreen mode

The App.tsx import mount function from react component, and when the App component mount, give the mount function a div.

Now, if you run the server, you'll see the following in the screen:

Image description

It's working! We have a React component rendered inside a Solid application.

That's it for part 1. In part 2 we'll solve to communication problem.

Credits

If you like the cute fish that I'm using, check out: https://thenounproject.com/browse/collection-icon/stripe-emotions-106667/.

Image description

Comments 0 total

    Add comment