I have seen that some people think that you have to forget about Rails and Hotwire, if you want to use React or another Javascript framework to build a Single Page Aplication, but this idea is not true.
You can use Rails and Hotwire as the base of your app, and plug your React app to be shown where is needed. In this way you can use both frameworks, and pick the one that you think that will help you must for each feature.
Here I want to explain how you can plug a react app inside a Rails app with Hotwire, using Stimulus.js.
To use a javascript bundler with rails, you can use the gem jsbundling-rails, it will help you to integrate esbuild, rollup.js, or Webpack to bundle your JavaScript, then deliver it via the asset pipeline in Rails.
If you are already familiar with one of those, use it, if you are not familiar, or you want to move away from webpack, I think that you could try esbuild.
I will continue this article explaining who you can integrate react using esbuild.
Following the instructions from the github page, you will need to:
jsbundling-rails
to your Gemfile with gem 'jsbundling-rails'
./bin/bundle install
./bin/rails javascript:install:esbuild
After this steps the installer should have updated your package.json
adding esbuild
as dependency and a build script, with the instruction to build your javascript from app/javascript
and put it on app/assets/builds
.
{
"name": "app",
"private": "true",
"dependencies": {
"@hotwired/stimulus": "^3.0.1",
- "@hotwired/turbo-rails": "^7.1.1"
+ "@hotwired/turbo-rails": "^7.1.1",
+ "esbuild": "^0.14.18"
+ },
+ "scripts": {
+ "build": "esbuild app/javascript/*.* --bundle --sourcemap --outdir=app/assets/builds"
+ }
}
Now you can go and install react with yarn:
yarn add react react-dom
It will update you package.json with react
and react-dom
.
{
"name": "app",
"private": "true",
"dependencies": {
"@hotwired/stimulus": "^3.0.1",
"@hotwired/turbo-rails": "^7.1.1",
- "esbuild": "^0.14.18"
+ "esbuild": "^0.14.18",
+ "react": "^17.0.2",
+ "react-dom": "^17.0.2"
},
"scripts": {
"build": "esbuild app/javascript/*.* --bundle --sourcemap --outdir=app/assets/builds"
}
}
You can now add a app/javasctipts/App.jsx
file to test the react integration. There you can add something like:
import * as React from 'react'
export default () => <h1>Hello, world!</h1>
And now add a app/javascript/controllers/hello_controller.jsx
that to display the App
component on connect
.
Note: 👉 If you already have one hello_controller.jsx
, because you are on a new rails app, you can just change the file extension to .jsx
.
import { Controller } from "@hotwired/stimulus"
import * as React from 'react'
import * as ReactDOM from "react-dom";
import App from "./../app"
export default class extends Controller {
connect() {
ReactDOM.render(<App />, this.element);
}
}
And now on a page add a tag for the hello_controller
to display the react App
component.
<div data-controller="hello">
</div>
At this moment you should see “Hello world!” on the page if you visit the page.
By starting the react app with Stimulus.js we can then move to other pages, that don’t have any react component, and then go back to the page with react using turbo, and Stimulus.js will initialize the react app as expected.
You can add more jsx
files, directly on app/javascripts
.
For example if you add a Counter.jsx
with:
import * as React from 'react';
import { useState } from 'react';
export default function Counter() {
const [count, setCount] = useState(0);
function handleClick() {
setCount(count + 1);
}
return (
<button onClick={handleClick}>
Clicked {count} times
</button>
);
}
You can then import it on the App.jsx
with…
import * as React from 'react'
import Counter from "./Counter";
export default () => <h1>Hello, world!</h1>
And with that you can now can use that Counter
.
For example, we can change the App
function to something like this…
import * as React from 'react'
import Counter from "./Counter";
export default function App() {
let style = {
border: "1px solid red",
padding: "1rem"
}
return (
<div style={style}>
<h1>This is a React App!</h1>
<p>This are some react counters!</p>
<Counter />
<Counter />
<Counter />
</div>
);
}
To show something like this…
I built a little example app with the last example.
You can get the code of the example app on github.com/bhserna/hotwire-react.
It is a demo that shows how you can plug a react app inside a rails app using Stimulus.js.
If you go to the “page with react” you will see a page with the counters implemented using react and started using a Stimulus controller.
As we saw in this post, by starting the react app with Stimulus.js you can then move back to the index, that don’t have any react component, and the go back to the page with react using turbo, and Stimulus.js will initialize the app as expected.
Like in this video…
Here I try to share knowledge and fixes to common problems and struggles for ruby on rails developers, like How to fetch the latest-N-of-each record or How to test that an specific mail was sent or a Capybara cheatsheet. You can see more examples on Most recent posts or all posts.