Kyle Fritz
baltimore technologist. gravity skeptic. director of reverse engineering at @OrderUp.
@kylefritz
http://bit.do/nation-react
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
Simple, fast, light
ideally :)
Professional Users
Powerful dashboards
Logistics & Optimization
Lots of Data
Real-Time Updates
Not "too much clicking"
class CartItem extends Marionette.ItemView
template: App.jst('cart_item')
modelEvents:
'change':'render'
class CartItems extends Marionette.CollectionView
itemView: CartItem
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>
Usual pattern:
class Driver extends Marionette.ItemView
template: ...
modelEvents:
'change:name': 'render'
class Driver extends Marionette.ItemView
template: ...
modelEvents:
'change:name': 'render'
'change:availability': 'render'
'change:deliveryServiceId': 'render'
'change:location': 'renderLastUpdatedAtBadge'
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'
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.
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.
When the data changes, React conceptually hits the "refresh" button, and knows to only update the changed parts.
This is like Rails server views!
Your data
Models, collections, what have you
Most of your stuff
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
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>
http://facebook.github.io/react/docs/reconciliation.html
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>
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>
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))
)
)
);
}
});
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
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
#= 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
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
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
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.
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 .
#= 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
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
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
Your Rails variables in your JS
https://github.com/gazay/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
# 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
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
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)
)
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
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
@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
https://github.com/kylefritz/react-on-rails
(read the commit logs for all the reference links)
class Views.Messages extends Marionette.View
className: 'chat-pane-wrapper'
onShow: =>
componentElement = <MessagesList
collection={@collection} />
React.render(componentElement, @el)
onDestroy: =>
React.unmountComponentAtNode(@el)
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
By Kyle Fritz
Ruby Nation June 2015
baltimore technologist. gravity skeptic. director of reverse engineering at @OrderUp.