Decisions in react_rails_webpack: the renderLastComponentDiv function

reactrubyrailswebpack

Currently, the react_rails_webpack gem renders each react component with a call to the renderLastComponentDiv function. This call is made from a script tag inside the div with the component name and props information. That div might look something like this:

<div
    class="react-component-target"
    data-componentname="Hello"
    data-componentprops="{myProp: 'some value'}"
    >
    <script>
        renderLastComponentDiv()
    </script>
</div>

Generally, most people try to avoid burying script tags in the DOM like this, and at first I was a little nervous about it, myself. Here’s how it came about.

When I first wrote the integration that ended up in react_rails_webpack, it didn’t work this way. Instead, there was simply some JavaScript that would grab every HTML element with the react-component-target tag and render accordingly. This meant that all of the non-react page content loaded before any React components started rendering. The same way that slow-loading images can make page elements jump around when they finally show up, the late-rendering React elements could make the page suddenly rearrange in jarring ways.

At first, I thought this might only be resolvable by implementing server-side rendering, which I didn’t want to get into (that stuff looks haaaard!). Then it occurred to me to just write a JavaScript function to call from a script tag inside the component element. Just grab the parent and render! Unfortunately, that isn’t as simple as it sounds, because as far as I can tell, there’s no easy way to say “get the parent of the element currently rendering”. Once I realized that, I thought I might just be stuck. Then I hit on the idea to try the current implementation.

The way renderLastComponentDiv works is it searches the DOM for the very last element with a class of react-component-target, and renders a component in it. You might think this would always render the last element with the react-component-target class listed in the HTML file. But here’s the trick: the last element in the DOM with a class of react-component-target is always going to be the parent of the script tag in which renderLastComponentDiv is running, because nothing after that has yet rendered.

I wasn’t sure it would work in practice, but I tested it out and sure enough, it did! Now the components render in order as the rest of the HTML renders, and there’s none of that out-of-order elements-jumping-around nonsense!