DEV Community

Alex Wiles
Alex Wiles

Posted on • Originally published at alex.uncomma.com

Rendering a React component in Rails without a library

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:

<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.

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:

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.

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

Now we write the mounting function:

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

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.

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

Top comments (1)

Collapse
 
masciugo profile image
Corrado Masciullo

Hi Alex

I was going to apply your same approach as what I only need is DRYing my code. I just wonder if your are still satisfied with your solution or you have found some others or fallback to third party libs. Thank you