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!