DEV Community

JT Dev for JetThoughts

Posted on • Updated on • Originally published at jetthoughts.com

React + Ruby on Rails without any gems

React + Ruby on Rails without any gems

How to start using React components written in TypeScript using Ruby on Rails as a server with only built-in Rails features? There are a couple of ways we can achieve it with.

Photo by [Gene Devine](https://unsplash.com/@devine_images?utm_source=medium&utm_medium=referral) on [Unsplash](https://unsplash.com?utm_source=medium&utm_medium=referral)

One is to add a gem: react-rails or react_on_rails for example. But there is a way to have a simple integration without using these gems.

Setup

We will use Webpacker or JavaScript compiler and assets management. By following the webpacker installation guide, let’s add Webpacker and React to our application.

Now, create a new Rails application with a --webpack=reactoption:

    rails new myapp --webpack=react
Enter fullscreen mode Exit fullscreen mode

For existing project add gem 'webpacker' to Gemfile and run the following commands:

    bundle exec rails webpacker:install
    bundle exec rails webpacker:install:react
Enter fullscreen mode Exit fullscreen mode

Next step is to install typescript by running typescript installer:

    bundle exec rails webpacker:install:typescript
    yarn add @types/react @types/react-dom
Enter fullscreen mode Exit fullscreen mode

Finally, convert file generated by rails hello_react.jsx to hello_react.tsx and voila — we have everything we need to build our application.

Render component

Let's build a simple component which alerts a message when we are clicking on it.

    function Welcome(props) (
      <button onClick={() => alert(props.message)}>
        Click on me!
      </button>
    )
Enter fullscreen mode Exit fullscreen mode

Here’s what we get at the beginning. This code shows how to append your React component to the DOM after its initialization.

    document.addEventListener('DOMContentLoaded', () => {
      ReactDOM.render(
        <Hello name="React" />,
        document.body.appendChild(document.createElement('div'))
      )
    })
Enter fullscreen mode Exit fullscreen mode

Let's just copy and slightly adjust it to our needs. Instead of appending to the div tag to the body, we will mount the component to the needed node.

    document.addEventListener('DOMContentLoaded', () => {
      ReactDOM.render(
        <Welcome message="Hello, kind user!" />,
        document.getElementById('welcome-button')
      )
    })
Enter fullscreen mode Exit fullscreen mode

The only missing part is the actual mount node on the page.

    <div id="welcome-button"></div>
Enter fullscreen mode Exit fullscreen mode

Looks like everything is good and our small component should be rendered and welcome us, but we forgot something. JavaScript file with the component is not being loaded, so nothing really happens when the page loading. In the javascript/packs/application.js we will add a line which will require a file with the Welcome component:

    import 'containers/Welcome'
Enter fullscreen mode Exit fullscreen mode

Let's see on the page and voila — our component is locked and loaded. Actually, if you are using bin/webpacker-dev-server you don't need to reload anything, it will do it for yourself.

What if we want to have our Welcome component only on the page we are using? Well, you can just add a new pack with only the Welcome component required and add a javascript tag javascript_pack_tag 'welcome' inside a view. What is pack anyway? This is Webpacker name for Webpack entry point concept.

Pass props

What if we need to pass data to our component? I.e. pass some props. The answer is data attributes. We will add a custom message as a data attribute to our mounted node:

    <div id="welcome-button" data-message="Hello from rails view!"></div>
Enter fullscreen mode Exit fullscreen mode

And use this data to render the component:

    const mountNode = document.getElementById('welcome-button');
    const message = mountNode.getAttribute('data-message');

    document.addEventListener('DOMContentLoaded', () => {
      ReactDOM.render(
        <Welcome message={message} />,
        mountNode
      )
    })
Enter fullscreen mode Exit fullscreen mode

Don’t forget about type annotations

Hey, we are actually using typescript, so let's throw in some type annotations sparkles. Here is a complete hello_react.tsx file:

    import React from 'react'
    import ReactDOM from 'react-dom'

    interface WelcomeProps {
      message: string
    }

    export const Welcome: React.FC<WelcomeProps> = ({ message }) => (
      <button onClick={() => alert(message)}>
        Click on me!
      </button>
    )

    const mountNode = document.getElementById('welcome-button');
    const message = mountNode.getAttribute('data-message');
    document.addEventListener('DOMContentLoaded', () => {
      ReactDOM.render(
        <Welcome message={message} />,
        mountNode
      )
    })
Enter fullscreen mode Exit fullscreen mode

Can we do it better?

First of all, we will make a general helper method to render mounted nodes inside ApplicationHelper file:

    def react_component(name, props)
      content_tag(:div, { id: name, data: { react_props: props } }) do
      end
    end
Enter fullscreen mode Exit fullscreen mode

And use it inside of a view:

    <%= react_component('welcome-button', message: 'Hello from rails view!') %>
Enter fullscreen mode Exit fullscreen mode

Inside the JavaScript, we will also make a general function for mounting a component:

    function mount(Component, mountNodeId) {
      document.addEventListener('DOMContentLoaded', () => {
        const mountNode = document.getElementById(mountNodeId);
        const propsJSON = mountNode.getAttribute('data-react-props');
        const props = JSON.parse(propsJSON);

        ReactDOM.render(<Component {...props} />, mountNode);
      })
    }
Enter fullscreen mode Exit fullscreen mode

Using these two helper functions will reduce the amount of boilerplate code and have a simple and flexible abstraction over the rendering of React components.

Wrapping up

You can find the full project here: https://github.com/jetthoughts/react-typescript-rails-simple-integration

With this approach, we can easily mix Rails views and React components, or use React components as a replacement for the Rails views.

Webpacker might be confusing for the first time, especially if you don't have any experience with Webpack, but Rails team made an awesome job to hide complex stuff from you and provide an out-of-box tool. Hot reloading, ES6/typescript features, code splitting and the ocean of npm packages without any extra hassle will make your development experience more productive.

And the most important advantage, in my opinion, is lack of any dependency but Rails itself.

*Dmitriy Savin *is a Software Engineer at JetThoughts. Follow him on *LinkedIn *or GitHub.

If you enjoyed this story, we recommend reading our latest tech stories and trending tech stories.

Top comments (0)