REACT
ON
RAILS

@kylefritz

http://bit.do/nation-react

HI, I'm Kyle

I'm into bikes, gardening & being outside

I teach full-stack web development at Betamore in Rails or Node

I love seeing the internet jump out of computers and affect the real world

  • Crowd-sourced, restaurant food-delivery platform
     
  • We use our own driver pool to deliver good and interesting food to our customers
     
  • 30 cities +  1,000 drivers
    (not in the top 10 metros)

Consumer Apps

Simple, fast, light

ideally :)

Operations Apps

Professional Users

Powerful dashboards

Logistics & Optimization

Lots of Data

Real-Time Updates

Not "too much clicking"

We have a lot of internal tools

Our Internal Tools Fall into two categories

Kind 1: Good Olde Rails

Good Olde Rails

  • Fast development
  • Reliable & easy to test
  • Easy to scale up team

Kind 2: CraY TOWN

Kind 2: CraY TOWN

KIND 2: CRAY TOWN

  • Editing non-trivial data-structures
  • Real-time updates
  • Filter/Slice/Dice

What about you guys?

Write Javascript 2+ days a week?

Are Pretty Familar w/ BACKBONE?

MARIONETTE?

What about you guys?

Have "Looked Into" React?

Are "All-in" on React?

ADJUSTED Presentation

Reasons to consider
React for your application

Reasons to consider adopting
React for your application

  • Simple but organized
  • Data-driven UX
  • Supports rapid rendering
  • Ready for change

Backbone + Marionette

Marionette Concepts

class CartItem extends Marionette.ItemView
  template: App.jst('cart_item')
  modelEvents:
    'change':'render'

class CartItems extends Marionette.CollectionView
  itemView: CartItem

Marionette Concepts

class Warnings extends Marionette.ItemView
  template: App.jst('warnings')

class Cart extends Marionette.LayoutView
  template: App.jst('cart')

  regions:
    warnings: '[data-region=warnings]'
    items: '[data-region=items]'

  onShow: =>
    @warnings.show(new Warnings(model: @model.warnings()))
    @items.show(new CartItems(collection: @model.cartItems()))
<h2>Cart</h2>
<div data-region="items"></div>
<div data-region="warnings"></div>

Item Change

  1. Different property value set
     
  2. Two change events fired: changechange:property
     
  3. ItemView listens for change
     
  4. ItemView re-renders

Collection Change

  1. Item added to collection
     
  2. Collection fires event add
     
  3. CollectionView listens for add
     
  4. CollectionView intelligently appends a new ItemView

Marionette's Strength is 
COMPOSABLITY

Usual pattern:

  1. Start out with CollectionView & ItemView
     
  2. Complexity creeps in: ItemView => LayoutView
     
  3. LayoutView mounts CollectionViews
     
  4. Turtles the whole way down

Driver List

Layout + CollectionView

CollectionView of Layouts
(Of CollectionViews)

What Happens
WHEN A name Changes?

Re-render

WHEN A BIrthdate

CHANGES?

Re-render

change:name

class Driver extends Marionette.ItemView
  template: ...
  modelEvents:
    'change:name': 'render'

Lots of change:somethings

class Driver extends Marionette.ItemView
  template: ...
  modelEvents:
    'change:name': 'render'
    'change:availability': 'render'
    'change:deliveryServiceId': 'render'
    'change:location': 'renderLastUpdatedAtBadge'

This ruins Marionette

class Driver extends Marionette.ItemView
  template: ...
  modelEvents:
    'change:name': 'render'
    'change:availability': 'renderAvailability'
    'change:deliveryServiceId': 'renderDeliveryServiceId'
    'change:location': 'renderLastUpdatedAtBadge'

  renderAvailability: ->
    # DOM manipulation

  renderDeliveryServiceId: ->
    # DOM manipulation

  renderLastUpdatedAtBadge: ->
    # DOM manipulation
class CartItem extends Marionette.ItemView
  template: ...
  modelEvents:
    'change':'render'

Events Cause REndering Cascades

JS is Fast!
but
DOM IS SLOW!

Other Backbone + Marionette Pitfalls

  • Really have to learn all the classes & their interfaces
     

  • Starts off simple; gets complex to meet performance demands
     

  • The opposite of what makes jQuery great: Backbone focuses on data instead of UI + State.

REACT
CONCEPTS

Simple

Simply express how your app should look at any given point in time, and React will automatically manage all UI updates when your underlying data changes.

Declarative

When the data changes, React conceptually hits the "refresh" button, and knows to only update the changed parts.

 

This is like Rails server views!

@props

Your data

Models, collections, what have you

Most of your stuff

@state

ALL the UI specific state

Anything that is different if you had just refreshed the page

When you fetch information from server, store as state at one level and then as props below

Always Re-REnder

We're back!

class CartItem extends Marionette.ItemView
  template: App.jst('cart_item')
  modelEvents:
    'change':'render'

class CartItems extends Marionette.CollectionView
  itemView: CartItem
CartItem = React.createClass
  render: ->
   <div>{@props.name}</div>

Cart = React.createClass
  mixins: [Backbone.React.Component.mixin]

  render: ->
   <div>
     {@props.collection.map (item)-> <CartItem {..item} />}
   </div>

Rendering is A pure function of props and state

Virtual Dom

+

Reconcilation

http://facebook.github.io/react/docs/reconciliation.html 

Diff of Virtual Dom Produces Efficient, Imperative Updates

Has the same effect as
"Cell reuse" in Ios

Diff of Virtual Dom Produces Efficient, Imperative Updates

Marionette

  • Use fancy base classes
     
  • Separate out templates
     
  • Track property changes individually
     
  • Worry about UI state on render

REACT

NO

A home for UI State!

A HOME FOR UI STATE!

class Address extends React.component
  state:
    editing: false
    address: ''

  handleAddress: (event) =>
    @setState(address: event.target.value)

  handleToggleEditing: =>
    @setState(editing: not @state.editing)

  render: =>
    <div>
      <h3>Address</h3>
      {@state.editing
        ? <input value={@state.address} onChange={@handleAddress} />
        : <address>{@state.address}</address>
      }
      <a onClick={@handleToggleEditing}>Edit</a>
    </div>

render

@PROPS
@state

JSX is simpler to understand
AND more powerful

class PhotoGallery extends React.component
  render: ->
    <div className="photo-gallery">
      {@props.images.map (img) ->
        <Photo {..img} />}
    </div>


class Photo extends React.component
  ...
  render: ->
    ...
    <div className='photo'>
      <span>{@props.caption}</span>
      <br />
      <button onClick={@toggleLiked} className={buttonClass}>
        ♥
      </button>
      <br />
      <img src={@props.imageUrl} />
    </div>

w/ JSX

w/o JSX

JSX is just Syntax

var TodoApp = React.createClass({
  ...
  render: function() {
    return (
      <div>
        <h3>TODO</h3>
        <TodoList items={this.state.items} />
        <form onSubmit={this.handleSubmit}>
          <input onChange={this.onChange} value={this.state.text} />
          <button>{'Add #' + (this.state.items.length + 1)}</button>
        </form>
      </div>
    );
  }
});
var TodoApp = React.createClass({
  ...
  render: function() {
    return (
      React.createElement("div", null, 
        React.createElement("h3", null, "TODO"), 
        React.createElement(TodoList, {items: this.state.items}), 
        React.createElement("form", {onSubmit: this.handleSubmit}, 
          React.createElement("input", {onChange: this.onChange, value: this.state.text}), 
          React.createElement("button", null, 'Add #' + (this.state.items.length + 1))
        )
      )
    );
  }
});

AND react is Still Very much
Component Based and Composible

And wow is it performant!

And wow is it performant!

LIFECYCLE METHODS

  • getInitialState
  • componentWillMount
    invoked immediately before the initial rendering

  • componentDidMount
    Invoked immediately after the initial rendering occurs.
    Can use @getDOMNode() if you want to integrate with other JavaScript frameworks, set timers using setTimeout or setInterval, or send AJAX requests

  • componentWillUnmount
    Perform any necessary cleanup before a component is unmounted

UPDATING METHODS

  • componentWillReceiveProps
    react to updated props before render (animations)
  • shouldComponentUpdate
    block rendering
  • componentWillUpdate
  • componentDidUpdate

 

component spec is really small

REACT
on
RAILS

Strategies for organization/isolation of components

Transform JSX

There is a hot-linkable jsx transformer (just for fun)
 

Better: 

gem 'react-rails', '~> 1.0.0.pre',
      github: 'reactjs/react-rails'


gem 'sprockets-coffee-react'

 

Create a something.jsx or something.js.cjsx file

Hello React

#= require react

class HelloReact extends React.component
  render: ->
    <div>
      Hello React!!
    </div>


document.addEventListener "DOMContentLoaded", ->
  React.render <HelloReact />, document.getElementById('hello-react')

app/views/home/show.html.erb

<div id="hello-react"></div>

app/assets/javascripts/application.js.cjsx

React w/ View Helpers and react_UJS

app/views/home/show.html.erb

<%= react_component('HelloReact') %>

<!-- becomes: -->
<div data-react-class="HelloReact"></div>
#= require react
#= require react_ujs

class HelloReact extends React.component
  render: ->
    <div>
      Hello React!!
    </div>

app/assets/javascripts/application.js.cjsx

Some kind of module System?

app/assets/javascripts/orders/driver.js.cjsx

class Driver extends React.component
  render: ->
    # show a tiny summary

app/assets/javascripts/drivers/driver.js.cjsx

class Driver extends React.component
  render: ->
    # show the whole profile for a Driver

Some kind of module System?

suggestion: modulejs

modulejs is a lightweight JavaScript module system. It is not a module loader, it triggers no file system lookups or HTTP requests. It simply helps organizing code in small, maintainable and easy to use modules. Modules respect and resolve dependencies, the syntax is very similar to that of RequireJS. 

AMD-Style Loading

app/assets/javascripts/orders/driver.js.cjsx

modulejs.define 'orders/driver', ->

  class Driver extends React.component
    render: ->
      # show a tiny summary

app/assets/javascripts/drivers/driver.js.cjsx

modulejs.define 'drivers/driver', ['orders/driver'], (DriverBadge) ->

  class Driver extends React.component
    render: ->
      # show the whole profile for a Driver
      # can use <DriverBadge /> since imported

app/assets/javascripts/application.coffee

#= require modulejs
#= require_tree .

Super-Cool Logging

Zero other Features =>

Asset Pipeline loves it

Kicking off the Processs

#= require modulejs
#= require_tree .

modulejs.define 'entry_point', ['app'], (App)  ->
  return (el) ->
    React.render <App />, el
#delivery-chat

coffee:
  startApp = modulejs.require('entry_point')
  startApp(document.getElementById('delivery-chat'))

app/assets/javascripts/some_app/index.js.cjsx

app/views/some_app/show.slim

Organizational Tips

  • One React component per file makes them easier to find plus room for growth

  • ABC: Always be composing

  • Keep child components in a folder w/ their "orchestrating" component

data

between

React + Rails

React ujs:
Pass into second View Helpers arg to encode as a data-

app/views/home/show.html.erb

<%= react_component('HelloMessage', name: 'John') %>

<!-- becomes: -->
<div data-react-class="HelloMessage"
     data-react-props="<...quoted json for {name: 'John'}>"></div>
#= require react
#= require react_ujs

class HelloReact extends React.component
  render: ->
    <div>
      Hello {@props.name}!!
    </div>

app/assets/javascripts/application.js.cjsx

Option: Use Gon

Your Rails variables in your JS

https://github.com/gazay/gon

Option: Use Gon

app/controller/delivery_controller.rb

class DeliveryController < ApplicationController

  def show
    gon.push(driver: Driver.last)
  end

end
React.render <Driver {...gon.driver} />, document.getElementById('delivery-widget')

app/assets/javascripts/application.js.cjsx

But Which Attributes go in?

Consider Active Model Serializers

# ALPHABETIZE attributes & methods
class Dispatcher::DriverSerializer < Dispatcher::BaseSerializer

  attributes \
   :available,
   :banned,
   :phone,
   :platform

  def banned
    object.restaurant_bans.map(&:restaurant_id)
  end

  def phone
    number_to_phone(object.phone) if object.phone.present?
  end
end

AMS plays well w/ Data Exchange 

class SomeController < ApplicationController

  def show
    driver = Driver.find(params[:id])
    gon.push(driver: DriverSerializer.new(driver).serializable_object)
  end
end
class Driver
  after_save :pusher

  private

  def pusher
    Pusher.trigger('private-channel', 'driver',
        DriverSerializer.new(driver).serializable_object)
  end

end
class JsonOtherController < ApplicationController

  def show
    driver = Driver.find(params[:id])
    render json: driver, serializer: DriverSerializer
  end

end

Fetch

  class Driver extends React.Component
    state:
      loading: true

    componentDidMount: =>
      fetch("/drivers/#{@props.driverId}", {credentials: 'include'}
      ).then((r)-> r.json()
      ).then((driver) =>
        @setState(driver)
        @setState(loading: false)
      )

Alternative techniques

React w/ Server REndering

class @HelloReact extends React.component
  render: ->
    <div>
      Hello React!!
    </div>

app/views/home/show.html.erb

<%= react_component('HelloReact', {}, {prerender: true}) %>

app/assets/javascripts/components/hello.js.cjsx

#= require react
#= require react_ujs
#= require_tree .

app/assets/javascripts/application.coffee

//= require_tree ./components

app/assets/javascripts/components.js

Pushing Data to React

app/views/home/show.html.erb

<%= react_component('HelloReact', {important: @data}, {prerender: true}) %>
@HelloReact = React.createClass
  render: ->
    <div>
      Hello {@props.important}!!
    </div>

app/assets/javascripts/components/hello.js.cjsx

More sophisticated Asset Packaging

Firebase

@TodoApp = React.createClass
  # mixins: [ReactFireMixin]

  componentWillMount: =>
    @firebaseRef = new Firebase("https://ReactFireTodoApp.firebaseio.com/items/");
    @firebaseRef.on("child_added", (dataSnapshot) =>
      @items.push(dataSnapshot.val());
      this.setState(items: @items)

  render: =>
    # use @state.items in a meaningful way

React w/ Rails

  • Simple to learn (like rendering views and partials)
     
  • Easy to plug into your existing app in a small or big way
     
  • Performant enough to lean-in on

http://bit.do/nation-react

https://github.com/kylefritz/react-on-rails

(read the commit logs for all the reference links)

Thanks!

@kylefritz

Appendix

REACT
    +
Marionette

A marionette Adapter View

  class Views.Messages extends Marionette.View
    className: 'chat-pane-wrapper'

    onShow: =>
      componentElement = <MessagesList
        collection={@collection} />
      React.render(componentElement, @el)

    onDestroy: =>
      React.unmountComponentAtNode(@el)

Backbone.React.Component.mixin

DriverPopover = React.createClass
  # "collection" & "model" are treated as special props by this mixin
  mixins: [Backbone.React.Component.mixin]

  ...

  handleDoneEditingDeliveryService: (deliveryServiceId)->
    @getModel().save({deliveryServiceId})
    @setState(editingDeliveryService: false)
    
  render: ->
      # if passed model:      @props            == model.attributes
      # if passed collection: @props.collection == collection.toJSON()




# would normally use
class DriverPopover extends React.component
# but mixins not supported for class definitions

React on Rails: modular Javascript that is the right for your app

By Kyle Fritz

React on Rails: modular Javascript that is the right for your app

Ruby Nation June 2015

  • 2,208