REACT ON RAILS
Episode 1 on ∞
François Catuhe
Apéro Ruby Bordeaux (Meetup)
05 december 2017
WHY REACT?
BECAUSE OF THIS
git clone git@github.com:fcatuhe/airbnb-clone.git
yarn start
yarn # shortcut for yarn install
LET'S SEE IT BY OURSELVES
and go to localhost:3000
Start the server
Clone the repo
Install the dependencies
(you need to have Yarn installed)
cd airbnb-clone
OTHER REASONS - 1/2
- It's supported by a strong community, and Facebook
- It's "just" a front library, coupled to no specific back-end
- Components are magic / highly reusable
OTHER REASONS - 2/2
- Virtual DOM and Flux data flow are here to last
- Still fragmented (= innovation), but already some concensus, e.g. Redux, React-Router, Redux-Form
FUN FACTS
- The output of a React deployment consists of 3 files:
- index.html (almost empty)
- main.js
- main.css
- You have exported some execution load from your server to the clients
- It works on GitHub Pages! https://fcatuhe.github.io/airbnb-clone/
WHY USE IT WITH RAILS?
BECAUSE COOL MEETS HOT 🔥
HOW TO USE IT WITH RAILS?
3 WAYS
- As standalones
- Rails API on one server
- React on a second server
- Separate but on the same server
- e.g. React resides in /app/client
- build React on deploy
- Integrated in Rails MVC pattern
- use React components
in Rails views
- use React components
Good option if you
start from zero
Better suited for
mature projects
Ideal for existing
Rails applications
INTEGRATION IN RAILS MVC PATTERN
Once a champion Sprockets is now aging
- still very helpful for your assets, but a bit outdated
- does not support modern Javascript, aka ES6 & ES7
- as a consequence, does not integrate well with React
Problem
- natively
- with react-rails gem from ReactJS
- with react_on_rails gem from ShakaCode
YAY! YOU’RE ON RAILS 5.1!
Rails 5.1 loves Javascript
- Hello Yarn, goodbye Rails-Assets / Bower
- Welcome Webpacker, happy retirement Sprockets
- Thanks for the tailored configuration of React
Again, 3 ways to integrate React:
LET'S LIVECODE!
RAILS BOILERPLATE - 1/2
cd .. # careful, get out of /airbnb-clone
rails g scaffold Flat name image_url price:integer price_currency lat:float lng:float
Install RestClient
bundle
Create the app
Scaffold the Flats
# Gemfile
gem 'rest-client'
rails new react-on-rails-demo --webpack=react --database postgresql -T
cd react-on-rails-demo
RAILS BOILERPLATE - 2/2
# /db/seeds.rb
Flat.destroy_all
flats_json = RestClient.get('https://raw.githubusercontent.com/lewagon/flats-boilerplate/master/flats.json')
flats_parsed = JSON.parse flats_json
flats = flats_parsed.map { |flat_parsed| flat_parsed.transform_keys(&:underscore) }
flats.each { |flat| Flat.create(flat) }
rails db:create db:migrate db:seed
Prepare to seed the flats
Create, migrate and seed
Your site is ready!
BRING BACK REACT
OPTIONAL
CONFIGURATION THAT SAVES YOUR LIFE
- ESLint (== RuboCop)
- Prettier (== Guard + RuboCop)
Both with Atom packages available
GO STEEL THE COMPONENTS
Copy-paste
- App.js
- App.css
- components folder
from
to

/airbnb-clone/src
/react-on-rails-demo/app/javascript/
BRING IN THE PACK
Import the packs (with style 😉) on your flats index
<!-- /app/views/flats/index.html.erb -->
<p id="notice"><%= notice %></p>
<h1>Flats</h1>
<div id="flats"></div>
<%= javascript_pack_tag 'flats' %>
<%= stylesheet_pack_tag 'flats' %>
...
Create the flats pack
// /app/javascript/packs/flats.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from '../App';
ReactDOM.render(<App />, document.getElementById('flats'));
Serve and try it out: localhost:3000/flats
GOTCHA!
We introduced a javascript dependency!
yarn add google-map-react
Refresh localhost:3000/flats and
Enjoy the magic!
Let's solve this with Yarn
Hey, but what if I want to make use of the amazing flats in my database instead?
REACT_ON_RAILS GEM
WHAT DO YOU GET FOR 0$ NOWADAYS?
- Passing @variables to components as props
- Server Side Rendering (SSR)
- I18n support
- Redux and React-Router integration
- Actually useful error messages in javascript land
- Test helpers
All this from the amazing ShakaCode react_on_rails gem
BRING IN HAWAÏ
# Gemfile
gem 'react_on_rails', '10.0.2' # prefer exact gem version to match npm version
rails generate react_on_rails:install
Watch the errors! Bundle and Yarn manually if needed
Serve and try it out: localhost:3000/hello_world
Commit all your work up to now (required by the gem)
git add .
git commit -m "Prepare for react_on_rails gem configuration"
Install the gem
bundle
Proceed with the installation
FLATS BUNDLE
Cut-paste App.js, App.css and components folder
Create flats-bundle.js on same pattern as hello-world-bundle.js
// app/javascript/packs/flats-bundle.js
import ReactOnRails from 'react-on-rails';
import Flats from '../bundles/Flats/App';
// This is how react_on_rails can see the Flats in the browser.
ReactOnRails.register({
Flats
});
/app/javascript
/app/javascript/bundles/Flats
from
to
MAKE USE OF REACT_ON_RAILS
Modify your flats index
<!-- /app/views/flats/index.html.erb -->
<p id="notice"><%= notice %></p>
<h1>Flats</h1>
<%= react_component("Flats", props: nil, prerender: false) %>
<%= javascript_pack_tag 'flats-bundle' %>
<%= stylesheet_pack_tag 'flats-bundle' %>
...
Serve, but using Foreman now
gem install foreman # if you don't have it already installed
foreman start -f Procfile.dev
GOTCHA!
rm ./bin/webpack ./bin/webpack-dev-server
bundle binstub webpacker --standalone
Unknown switches '-w'
Temporary fix here:
Serve again and try it out: localhost:3000/flats
Well, at least nothing's broken, but weren't we promised to use the flats from our database?
PASSING @VARIABLES AS PROPS
# /app/controllers/flats_controller.rb
...
def index
@flats = Flat.all
flats = @flats.map do |flat|
flat.as_json.transform_keys { |key| key.camelize(:lower) }
end
@flats_props = { flats: flats }
end
...
Add a @flats_props to our flats controller index action
Pass this @flats_props to the React component in the view
<!-- /app/views/flats/index.html.erb -->
...
<%= react_component("Flats", props: @flats_props, prerender: false) %>
...
ADAPTING THE FLATS COMPONENT
// /app/javascript/bundles/Flats/App.js
...
componentDidMount() {
const { flats } = this.props;
this.setState({
allFlats: flats,
flats,
selectedFlat: flats[0]
});
}
...
We need to modify the component to take this new prop into account, in the componentDidMount lifecycle method definition
using some ES6 destructuring and object literal shorthand syntax
Try it out: localhost:3000/flats
BOOM!
SERVER SIDE RENDERING - 1/2
Change react_on_rails config to make use of server-bundle.js
Create server-bundle.js and import the bundles
you want to be able to server render
# /app/javascript/packs/server-bundle.js
import './hello-world-bundle';
import './flats-bundle';
# /config/initializers/react_on_rails.rb
...
config.server_bundle_js_file = "server-bundle.js"
end
SERVER SIDE RENDERING - 2/2
In the views, allow the React components to prerender
Restart the server and play with SSR by
disabling Javascript in the Chrome dev tools settings,
for localhost:3000/hello_world and localhost:3000/flats
<!-- /app/views/flats/index.html.erb -->
...
<%= react_component("Flats", props: @flats_props, prerender: true) %>
...
<!-- /app/views/hello_world/index.html.erb -->
<h1>Hello World</h1>
<%= react_component("HelloWorld", props: @hello_world_props, prerender: true) %>
GOTCHA!
// /app/javascript/bundles/Flats/App.js
class App extends Component {
constructor(props) {
super(props);
const { flats } = props;
this.state = {
flats,
allFlats: flats,
selectedFlat: flats[0],
search: ''
};
}
...
- Flats : obvious, the componentDidMount lifecycle method is not triggered, we need to pass the props in the constructor,
then we can delete the now useless componentDidMount.
We have no more flats, and the map is not displaying
- Map: well, that's life, it really needs javascript to work
WHAT NEXT?
FIND ALL THIS CODE ON GITHUB
GOING FURTHER
The unbeatable 50 bucks to Stephen Grider:
📆📆📆
Rebuild the Flats component from scratch with
Sébastien Saunier, CTO Le Wagon: Youtube
10€
10€
10€
10€
10€
📆📆
📆📆📆
📆
📆📆
update rating
what you will build
THANK YOU AND
SEE YOU ON NEXT EPISODE
React on Rails - 1 / ∞
By François Catuhe
React on Rails - 1 / ∞
- 637