React on Rails

Harry Moreno

 

Consultant at the R3DM.com

 

  • Ruby
  • Javascript

Bundle thick Javascript

client

Modules aka Javascript Gems

JSON REST API

GET request

JSON response

Install Browserify-rails gem

app/assets/javascripts





app/assets/javascripts/components/





node_modules/





package.json





.buildpacks

Things added to a standard RoR project

Node Package Manager

Install Node.js

  • Started 2009
  • 100K gems
  • Started 2009
  • 140K modules
package.json

npm install

npm run task
Gemfile + Rakefile

bundle install

rake task
{
  "//": "package.json file",
  "scripts": {
    "test": "mocha"
  },
  "dependencies": {
    "dependency": "6.3"
  }
}
# Rakefile
task :test do
  ruby "test/unittest.rb"
end
# Gemfile

gem 'dependency', '6.3'

+

~

Point sprockets to our SPA

// app/assets/javascripts/application.js

// This is a manifest file that'll be compiled into application.js, which will include all the files
// listed below.
//
// Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
// or any plugin's vendor/assets/javascripts directory can be referenced here using a relative path.
//
// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
// compiled file.
//
// Read Sprockets README (https://github.com/sstephenson/sprockets#sprockets-directives) for details
// about supported directives.
//
//= require client
// require_tree .
// app/assets/javascripts/client.js

var React = require('react')
var Router = require('react-router')
var routes = require('./routes.jsx')

Router.run(routes, function (Handler) {
  React.render(React.createElement(Handler, null),
               document.getElementById('content'))
})

Initialize React onto a DOM element

In your view

<div id="content"></div>

In your view

<% if user_signed_in? %>
  <div id="content"></div>
<% else %>
  <div class="container">
    <div class="row">
      <div class="col-xs-12 col-xs-offset-0 welcome">

        <h1>Welcome</h1>

      </div>
    </div>
  </div>
<% end %>

Demo

CSRF

<%-# view.erb -%>

<%= csrf_meta_tags %>
<!-- renders -->

...

<meta name="csrf-token" content="MA2lN1qA==">
...
// globals.js
var $ = require('jquery')

function globular () {
  var csrf = $('meta[name="csrf-token"]')
               .attr('content')
  return {
    csrf: csrf
  }
}

module.exports = globular()

CSRF

var $ = require('jquery')
var globals = require('../globular.js')
var csrfToken = globals.csrf

$.ajax({
  url: 'logout',
  type: 'DELETE',
  headers: {
    'X-CSRF-Token': csrfToken
  },
  success: function(data) {
    debug('successful delete', data)
    window.location.replace(window.location.origin)
  }
})
.fail(function() {
  debug('logout failed')
})

You should namespace your SPA global variables. I use this approach.

Deploying to Heroku

Deploying to Heroku

bundle

npm install

Deployment time increases

Use a buildpack

Testing with Mocha

Testing with Mocha

To install
$ npm install --save mocha

To run
$ mocha spec/javascripts

or

package.json
{
  "scripts": {
    "test": "mocha spec/javascripts"
  }
}

$ npm run test

Rails JSON API

Rails JSON API

class ModelController < ApplicationController

  def model
    @model = Model.find(params[:id])
    render json: @model
  end

end
{
  "id": 108,
  "created_at": "2015-03-27T03:12:57Z",
  "updated_at": "2015-03-27T03:12:59Z"
}

Get /model/:id

Rails JSON API

class ModelController < ApplicationController

  def update
    @model = Model.find(params[:id])
    @model.update!(safe_params)
    render nothing: true
  end

  private

    def safe_params
      params.require(:model).permit(:foo)
    end

end
$.ajax({
  type: 'PUT',
  url: 'models/' + Id,
  headers: {
    'X-CSRF-Token': csrfToken
  },
  data: {model: {foo: 'bar'}},
  dataType: 'json',
  success: function (res) {
    console.log(res)
  }
})

PUT /model/:id

Pitfalls

Why React?

React.js

// app.jsx
var React = require('react')
var Searchbox = require('/searchbox.jsx')
var List = require('/list.jsx')
var ListItem = require('/listItem.jsx')
var ListTitle = require('/listTitle.jsx')

var App = React.createClass({
  render: function() {
    return (
      <div>
        <Searchbox />
        <List>
          <ListTitle title="Sporting goods"/>
          <ListItem name="football" price="49.99" />
          <ListItem name="baseball" price="9.99" />
          <ListItem name="basketball" price="29.99" />

          <ListTitle title="Electronics"/>
          <ListItem name="iPod Touch" price="99.99" />
          <ListItem name="iPhone 5" price="399.99" />
          <ListItem name="Nexus 7" price="199.99" />
        </List>
      </div>
    )
  }
})

module.exports = App

React.js

  • Immutability is good!
  • Don't focus on mutating DOM focus on rendering you component given any possible state
  • State changes cascade down from parent components to their children

React.js

Future Work

  • Remove jQuery
  • use Superagent for client HTTP requests
  • interoperate better with existing rails Javascript gems