Create a mini js app

Things to be comfortable with (easy):

If you invest time in these, they will save you both time & pain.

Purposes of this guide

  • Document how to pass Rails data to React
  • Separate Rails needs from React needs
  • Get you to the point where you can use any React guide or canned solution to build an app in harmony with our specific stack
  • Clearly connect the dots between Rails & React

Decide on a chunk name

We need a JS (camelCase) name and a Ruby (snake_case) symbol. I will use the Video page as a good example. The chunk is named setup_video_media. (naming could be better)

First we add our new chunk to the index of loadable chunks:

app/views/globals/_page_config.html.erb

<%# Inside the JSON config %>
lazyChunks: {
  // ...
  setupVideoMedia: @lazy_chunks[:setup_video_media].present?,
  // JS chunk name       Ruby chunk name
}

We then set @lazy_chunks[:setup_video_media] = true and expose our data to the client via set_react_props. In this case, it puts the data under window.reactProps.videos.

app/views/video_media/index.html.erb:14

<%
  @lazy_chunks[:setup_video_media] = true
  videos_mapped = @videos.map do |video|
    mapped_video = {
      title: video.title,
      # ect...
    }
    mapped_video[:topics].push(*video.category)
    mapped_video
  end
  set_react_props({ videos: videos_mapped })
%>

When the page loads, the JS will look at what chunks it needs to lazy-load. Important: This is the part where JS seizes control, you will not be able to use <%= %> in this, or anything that follows. Everything you need should be passed to set_react_props().

client/js/www/www.index.js:189

if (lazyChunks.setupVideoMedia) {
  import(/* webpackChunkName: "wwwVideosLazy" */
    /* webpackMode: "lazy" */
    './videos.index');
}

Now you are ready for the React part. See the last line, this is where our Rails data from set_react_props() actually meets React.

client/js/www/videos.index.js:13

// Video Page
import React, { Component } from 'react'; // Required
import { render } from 'react-dom'; // Required only on the top-level React component

// Standard react boilerplate
class Videos extends Component {

  render() {
    //...JSX here
  }
}

// ...

render(<Videos {...window.reactProps} />, document.getElementById('Videos'));
//     ^Class Name   ^Data from Rails              ^The Element your app will load in

{...window.reactProps} Uses the spread operator to set all *keys passed to reactProps (videos in this case) as props available to your component.

In this case, this.props.videos is from window.reactProps.videos, which is set by rails in set_react_props({ videos: videos_mapped }).

Elsewhere inside the app, you would do something like:

this.setState({
  loaded: true,
  videos: this.props.videos
});

FAQ

  • Please let me know if I need to add anything

Do what you want from here

If you utilize chunk isolation in this way, your builds will be fast & you don't need to worry about affecting other pages.

You could even be a rebel and use Vue.js with this same flow instead of React.

If you follow these principles the JS is very forgiving.