Setting up React with Rails-7 using esbuild.

Setting up React with Rails-7 using esbuild.

·

4 min read

Prerequisites for local setup:

  • rails 7 installed
  • yarn installed

Create a new rails app by the command:

rails new esbuildApp -j esbuild

Now let us look at the Package.json and Procfile.dev

Package.json

package.json has our js dependencies and scripts

  "dependencies": {
    "@hotwired/stimulus": "^3.1.0",
    "@hotwired/turbo-rails": "^7.1.3",
    "esbuild": "^0.15.7"
  },
  "scripts": {
    "build": "esbuild app/javascript/*.* --bundle --sourcemap --outdir=app/assets/builds --public-path=assets"
  }

The esbuild command here basically just imports the stuff from app/javascript/* & runs a bundle command, a source map command and then it throws it all inside of app/assets/builds with the public path of assets.

This is important because inside of app/assets/builds you have application.js and application.js.map basically spits everything out here then the rails asset pipeline uses it.

Procfile.dev

If you only start your application with a bundle exec rails s it's not going to run the yarn build --watch The yarn build command inside of our package.json is the command which delivers our javascript into the asset pipeline. The --watch flag in the command watches for changes when you update your application code for hot reloading.

We can run your Procfile.dev by the command: bin/dev which uses the foreman gem to run both of the below processes in two pids. Firstly it runs the yarn command to throw everything into the source map. Then it starts the rails server.

Screenshot 2022-09-18 at 12.28.36.png

Screenshot 2022-09-18 at 12.29.17.png

Now let us stop the server with control+c.

Let's set up a home page and add some javascript to it

To set up a home page using the rails generator run the command:

rails g controller pages home

Now let's update the routes.rb file, to point the localhost:3000/ request to pages/home

Rails.application.routes.draw do
  root 'pages#home'
end

To make javascript appear on the screen, let's add a javascript controller using stimulus generator and run the command:

rails g stimulus react

so this creates our app/javascript_controllers/react_controller.js

Now let's update the react_controller.js by adding a console.log to test the javascript changes reflect on our app's root page.

import { Controller } from "@hotwired/stimulus";

// Connects to data-controller="react"
export default class extends Controller {
  connect() {
    console.log("React controller connected");
  }
}

Now update the home view to inject the react controller javascript code. Go to app/views/pages/home.html.erb and make the below changes:

<h1>Pages#home</h1>
<p>Find me in app/views/pages/home.html.erb</p>
<%= content_tag(:div, "", id:"app", data: {
    controller: "react"
}) %>

We are using a content_tag. In the content tag we have to tell it what type of content by providing the below info:

  • type: :div,
  • id: app
  • Which stimulus controller to use. In our case: app/javascript/controllers/react_controller.js. We don't need the _controller part so we just type controller: "react"

Restart the server and run the command: bin/dev you should now be able to see the log: "React controller connected" in the browser console.

Screenshot 2022-09-18 at 12.32.03.png

Screenshot 2022-09-18 at 12.35.51.png

Now let us stop the server with control+c.

Now to add some actual react components.

Firstly let's add react & react-dom packages with the command:

yarn add react react-dom

Now check the changes in Package.json

  "dependencies": {
    "@hotwired/stimulus": "^3.1.0",
    "@hotwired/turbo-rails": "^7.1.3",
    "esbuild": "^0.15.7",
    "react": "^18.2.0", // this is new
    "react-dom": "^18.2.0" // this is new
  },

Create App.jsx inside app/javascript/components/ folder by running the commands:

mkdir app/javascript/components/
touch app/javascript/components/App.jsx

Let us create a basic counter in the App.jsx with a button to increment the count state and a <p>tag displaying it:

import React, { useState } from "react";

function App() {
  const [count, setCount] = useState(0);
  return (
    <div>
      <p>You clicked {count} times!</p>
      <button onClick={() => setCount(count + 1)}>Click me</button>
    </div>
  );
}

export default App;

Now let us import the App.jsx into the react_controller.js and render it onto the app element using the createRoot from react-dom/client


import { Controller } from "@hotwired/stimulus";
import React from "react";
import { createRoot } from "react-dom/client";
import App from "../components/App";

// Connects to data-controller="react"
export default class extends Controller {
  connect() {
    console.log("React controller connected");
    const app = document.getElementById("app");
    createRoot(app).render(<App />);
  }
}

In the console we are getting an error:

Screenshot 2022-09-18 at 12.38.50.png

Now to fix the esbuild loader error by updating the package.json build script to:

  "scripts": {
    "build": "esbuild app/javascript/*.* --bundle --sourcemap --outdir=app/assets/builds --public-path=assets --loader:.js=jsx"
  }