DEV Community

Cover image for React Component Mounting with Rails: Simplifying Dynamic Interfaces
Sabber Hossain
Sabber Hossain

Posted on

React Component Mounting with Rails: Simplifying Dynamic Interfaces

Modern apps often require dynamic interfaces that can handle API calls, dynamic forms, and routing, and React is an excellent choice for this. However, React’s SPA (Single Page Application) approach doesn’t align well with Ruby on Rails, a full-stack framework that renders content server-side. While you can use React with Rails by setting up an API backend or using Webpack integration, this setup can add significant complexity and development time.

The Challenge
In projects like Raive and Dunu506, we needed dynamic features like complex wizards that required real-time interaction. Rails' traditional server-side rendering was cumbersome for these use cases, and React's dynamic nature was a clear fit.

But how could we use React without going full SPA? The solution is simple: we can mount React components in Rails views without abandoning Rails' server-side rendering approach.

The Solution
React components can be mounted on specific elements in a server-rendered view. Here's how we started:

Basic Mounting: In the app/javascript/packs/application.js, we mounted a simple React component:

import React from 'react'
import ReactDOM from 'react-dom'
import Hello from './Hello'

document.addEventListener('DOMContentLoaded', () => {
  const div = document.querySelector('#hello')

  if (div) {
    const props = JSON.parse(div.getAttribute('data-props'))
    ReactDOM.render(
      <Hello {...props} />,
      div
    )
  }
})
Enter fullscreen mode Exit fullscreen mode

Reusability: We then created a reusable function to mount components dynamically:

import MyReactComponent from './MyReactComponent'

function mountComponent(id, Component) {
  document.addEventListener('DOMContentLoaded', () => {
    const div = document.querySelector(id)

    if (div) {
      const props = JSON.parse(div.getAttribute('data-props'))
      ReactDOM.render(
        <Component {...props} />,
        div
      )
    }
  })
}
Enter fullscreen mode Exit fullscreen mode

mountComponent('#my-react-component', MyReactComponent)
Handling Multiple Components: For cases where we wanted to reuse the same component in multiple places, we generalized the approach:

function mountComponent(selector, Component) {
  document.addEventListener('DOMContentLoaded', () => {
    const elements = document.querySelectorAll(selector)

    elements.forEach(element => {
      const props = JSON.parse(element.getAttribute('data-props'))
      ReactDOM.render(
        <Component {...props} />,
        element
      )
    })
  })
}
Enter fullscreen mode Exit fullscreen mode

Avoiding Conflicts: Instead of using id or class, we decided to use a custom data-component attribute to avoid conflicts with other CSS or JS classes:

function mountComponents(components) {
  document.addEventListener('DOMContentLoaded', () => {
    const roots = document.querySelectorAll('[data-component]')

    Array.from(roots).forEach((root) => {
      const props = JSON.parse(root.dataset.props)
      const Component = components[root.dataset.component]

      ReactDOM.render(
        <Component {...props} />,
        root
      )
    })
  })
}

mountComponents({
  MyReactComponent
})
Enter fullscreen mode Exit fullscreen mode

The Result
This solution provided a clean, reusable way to mount React components in Rails views. It allowed us to leverage the dynamic power of React while maintaining the simplicity of Rails server-side rendering. We packaged this approach into a library that can be used in any project, saving development time and streamlining our workflow.

Conclusion
By using a data-component attribute and creating a simple function to mount React components dynamically, we successfully combined the power of React with the traditional server-rendered approach of Rails. This method is flexible, reusable, and clean, making it a great choice for teams needing dynamic interfaces in a Rails app.

Feel free to check out our GitHub repository for more details and to collaborate on this project!

Top comments (0)