REACT
ON
RAILS

@kylefritz

http://slides.com/kylefritz/deck

Motivation
TO USE
REACt

Consumer

Simple, fast, light

ideally :)

Operations

Professional Users

Powerful dashboards

Lots of Data

Real-Time Updates

Not "too much clicking"

Wishlist

  • Reusable Components
  • Understandable, Modular CSS
  • Data-Driven UX
  • Real-Time Updates
  • Not a lot of Typing

We choose 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

Wishlist

  • Reusable Components
  • Understandable, Modular CSS
  • Data-Driven UX
  • Real-Time Updates
  • Not a lot of Typing

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

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.

@props

Your data

Models, collections, what have you

Most of your stuff

@state

UI specific state

At first it seems like you should store lots of stuff in here but really, you want props :)

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

Marionette

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

REACT

NO

render

@PROPS
@state

But react is Still Very much
Component Based and Composible

JSX is simpler to understand
AND more powerful

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


Photo = React.createClass
  ...
  render: ->
    ...

    <div className='photo'>
      <span>{@props.caption}</span>
      <br />
      <button onClick={@toggleLiked} className={buttonClass}>
        ♥
      </button>
      <br />
      <img src={@props.imageUrl} />
    </div>

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

 

Other 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

REACT
on
RAILS

Getting Started

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 jquery
#= require react
#= require_tree .

HelloReact = React.createClass
  render: ->
    <div>
      Hello React!!
    </div>

$ ->
  React.render(<HelloReact />, $('#hello-react')[0])

app/views/home/show.html.erb

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

app/assets/javascripts/application.js.cjsx

React w/ react_UJS

@HelloReact = React.createClass
  render: ->
    <div>
      Hello React!!
    </div>

app/views/home/show.html.erb

<%= react_component('HelloReact') %>

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

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

app/assets/javascripts/application.coffee

React w/ Server REndering

@HelloReact = React.createClass
  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

REACT
    +
Marionette

A marionette Adapter View

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

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

    onDestroy: =>
      React.unmountComponentAtNode(@el)

Backbone.React.Component.mixin

  DriverPopover = React.createClass
    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()

case study:

NESTED
REaltime
updates

 

Data

cart = {
  address: '2619 St Paul St'
  items:[
    {name: 'Pillows', quantity: 6}
    {name: 'Sheets', quantity: 1}
    {name: 'Blankets', quantity: 2}
    {name: 'Side-Table', quantity: 2}
    {name: 'Bed', quantity: 1}
  ]
}

React

Item = React.createClass
  render: ->
    <div>
      <strong>{@props.name}</strong>
      <span style={paddingLeft: '10px'}>x{@props.quantity}</span>
    </div>

Cart = React.createClass
  render: ->
    <div>
      <h4>Address</h4>
      <address>{@props.address}</address>
      
      <h4>Items</h4>
      {@props.items.map((item)-> <Item {...item}/> )}
    </div>

Modifications

$ ->
  React.render(<Cart {...cart} />, $('#hello-react')[0])


# api

@addItem = () ->
  cart.items = cart.items.concat({name: 'Bed', quantity: 1})
  React.render(<Cart {...cart} />, $('#hello-react')[0])

@changeName = (i, name) ->
  cart.items[i].name = name
  React.render(<Cart {...cart} />, $('#hello-react')[0])

Rendering so little

case study:

LINKED

EDITOR UX

Edit in place

Cart = React.createClass
  ...
  render: ->
    address = if @state.editing
      <div>
        <input ref="addressField" onChange={@updateAddress}
            value={@props.address} />
        <a onClick={@exitEditMode}>Done</a>
      </div>
    else
      <div>
        <address>{@props.address}</address>
        <a onClick={@enterEditMode}>Edit</a>
      </div>

    <div>
      <h4>Address</h4>
      {address}
      ...
    </div>

Edit in place

Cart = React.createClass
  getInitialState: -> {editing: false}
  enterEditMode: ->   @setState(editing: true)
  exitEditMode: ->    @setState(editing: false)

  updateAddress: ->
    changeAddress(@refs.addressField.getDOMNode().value)

  # <input ref="addressField" onChange={@updateAddress}
  #        value={@props.address} />
  # <a onClick={@exitEditMode}>Done</a>
  ...

Edit in place

Cart = React.createClass
  getInitialState: -> {editing: false}
  enterEditMode: ->   @setState(editing: true)
  exitEditMode: ->    @setState(editing: false)

  updateAddress: ->
    changeAddress(@refs.addressField.getDOMNode().value)

  render: ->
    address = if @state.editing
      <div>
        <input ref="addressField" onChange={@updateAddress} value={@props.address} />
        <br/><a onClick={@exitEditMode}>Done</a>
      </div>
    else
      <div>
        <address>{@props.address}</address>
        <br/><a onClick={@enterEditMode}>Edit</a>
      </div>

    <div>
      <h4>Address</h4>
      {address}

      <h4>Items</h4>
      {@props.items.map((item)-> <Item key={item.name} {...item}/> )}
    </div>

Edit in place

@changeAddress = (address) ->
  cart.address = address
  React.render(<Cart {...cart} />, $('#hello-react')[0])

Wishlist

  • Reusable Components
  • Data-Driven UX
  • Real-Time Updates
  • Not a lot of Typing

http://slides.com/kylefritz/deck

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

(read the commit logs for all the reference links)

Thanks!

@kylefritz

React on Rails

By Kyle Fritz

React on Rails

Bmore on Rails March 2015

  • 3,088