Benito Serna Tips and tools for Ruby on Rails developers

How to use react with hotwire?

February 28, 2022

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.

You will need a javascript bundler

To use a javascript bundler with rails, you can use the gem jsbundling-rails, it will help you to integrate esbuildrollup.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.

Install jsbundler and esbuild tool

Following the instructions from the github page, you will need to:

  1. Add jsbundling-rails to your Gemfile with gem 'jsbundling-rails'
  2. Run ./bin/bundle install
  3. Run ./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"
+  }
 }

Install react and react dom

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"
   }
 }

The hello world

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.

Adding more than one file

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…

This is a react app

Example app

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…

Related articles

Weekly tips and tools for Ruby on Rails developers

I send an email each week, trying 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 post by topic.