Rendering a React component in Rails without a library
Tags: react, rails, javascript, ruby
Published: 2020-05-02 06:16:58

I have a Rails application where I render React components on a standard Rails view. Instead of pulling in a 3rd party library to manage the rendering, I wrote a few simple functions myself.

### How it works
In the Rails view, I create an element that includes all the data needed to render the component. Then a Javascript function finds these special nodes in the page, and mounts the components.

### Rendering the special element in a Rails View
The rendered HTML will look like this:
```html
<div class="react-MyReactComponent" data="{"message": "Hello, World."}"></div>
```

The class name is how the Javascript mounting function will find the elements. The data property holds the component props as a JSON object.

In my application helper, I created a simple function to generate these elements.

```ruby
module ApplicationHelper
  def react_component(component_name, data={})
    json_data = data.to_json
    class_name = "react-#{component_name}"
    content_tag(:div, nil,class: class_name, data: json_data).html_safe
  end
end
```

The function takes two arguments: the component name and the data object. The data object is a plain Ruby hash that is serialized to JSON and I prefix the component name with "react-". Then I just return a div tag with that class name and the data attribute.

Calling it in a slim view looks like this:

```slim
div
  h1 Hello, I have a message for you
  = react_component "MessageDisplay", {message: "hello, world!"}
```

### Writing the Javascript
I am not going to cover setting up the javascript build system in this post, but there are a lot of resources that document webpacker.

```javascript
The simple React component we will use:
const MessageDisplay = ({ message }) => (
  <div>{message}</div>
);
```

Now we write the mounting function:
```javascript
export const mountComponent = (component, componentName) => {
  const nodes = Array.from(
    doc.getElementsByClassName(`react-${componentName}`)
  );

  nodes.forEach((node) => {
    const data = node.getAttribute("data");
    const props = data && data.length > 2 ? JSON.parse(data) : {};
    const el = React.createElement(component, { ...props }, []);
    node.innerHTML = ''
    return ReactDOM.render(el, node);
  });
};
```

The mountComponent function takes two arguments: component and componentName. Component is the actual React component function and the component name is a stringified version. We need both because Javascript build systems change component names during transpilation, so we need a static string name to associate with it.

First, we find all elements on the page that match the "react-<ComponentName>" class. This is how we connect the react_component view funciton with the Javascript.

Then, we iterate through the nodes, parse the data props as a JSON object, create a new element with the parsed data props, clear the nodes children, and render it on our target node. I clear the children to cover any edge cases where the javascript is fired but the page did not do a full reload. This is important if you are using turbolinks (more on that late).

Calling this Javascript function is as simple as:
```javascript
mountComponent(MessageDisplay, "MessageDisplay")
```

### When to call mountComponent
You will want to call it on some javascript event. I use turbolinks, so I am subscribed to the "turbolinks:load" event.

```javascript
document.addEventListener("turbolinks:load", () => {
  mountComponent(MessageDisplay, "MessageDisplay");
  mountComponent(AnotherComponent, "AnotherComponent");
  mountComponent(AThirdOne, "AThirdOne");
});
```

You may need to listen to different event like window load.

### Summary
It is actually quite simple to render React components without another library. The one thing to watch out for is making sure changes to your components props are reflected in your Rails code as well. I created a library class with pure functions to create the component prop hashes. It is very convenient having that logic all in one place and you can write tests against it. It will become very fragile if you create the prop hashes in the views like in this example.

That is all. Happy coding!

_originally published on [Uncomma](https://alex.uncomma.com/software-til/rendering-a-react-component-in-rails-without-a-library)_