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

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

François Catuhe

"Apéro Ruby Bordeaux"

Find us on Meetup

React on Rails - 1 / ∞

By François Catuhe

React on Rails - 1 / ∞

  • 637