Create a mini js app
Things to be comfortable with (easy):
- JS Arrow Functions
args => doSomething(args)(you know this as procs/lamdas from Rails) - Dynamic import:
import('./videos.index') - Class Properties, proposal: Stage 2 future tech
- Spread syntax (you know this as "splat" from Rails)
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.