Slide 1

Slide 1 text

R E A C T. J S O N R A I L S

Slide 2

Slide 2 text

Let’s talk about JavaScript! I feel like I won the lottery being able to talk about JavaScript at a Ruby conference. But there’s actually been a lot of talk about JavaScript at This RailsConf. Talk of pepper backpacks, the zombie apocalypse, and whether or not JavaScript belongs in your zombie apocalypse pepper backpack. I’d like to talk with you about how we tackle client-side code and how React has helped us write better Rails apps.

Slide 3

Slide 3 text

michael chan My name is Michael Chan

Slide 4

Slide 4 text

@chantastic I’m @chantastic on twitter, where you’ll find a younger, happier version of me.

Slide 5

Slide 5 text

@chantastic I’m @chantastic on twitter, where you’ll find a younger, happier version of me.

Slide 6

Slide 6 text

I work on an app called Services. It helps churches organize: * volunteers * music * and services.

Slide 7

Slide 7 text

Launched 2006 on Rails 1.13 It launched in 2006 on Rails 1.13

Slide 8

Slide 8 text

So our approach to JavaScript has been: Our approach to JavaScript has been…

Slide 9

Slide 9 text

So our approach to JavaScript has been: Yes; write some Yes; write some.

Slide 10

Slide 10 text

So, we know how sprinkles can turn…

Slide 11

Slide 11 text

into mountains. We needed some structure.

Slide 12

Slide 12 text

When we started our second app, in 2012, looked around to see what the bigger Rails shops were doing.

Slide 13

Slide 13 text

Shopify was writing a JavaScript MVC called Batman.js to power their new real-time admin. And we followed suite.

Slide 14

Slide 14 text

We built our second app as two: a Rails API and a browser client. But we discovered a new host of problems: 2 sets of everything. 2 models, controllers, routers, validations, these efforts are all doubled. And you don’t realize just how good form_for is until you no longer have it.

Slide 15

Slide 15 text

And your app really starts to feel like this. We didn’t really have two separate apps, we had 2 MVCs stacked on top of each other. A change in either app required modification in the other.

Slide 16

Slide 16 text

I asked the product manager of that project if we had any cards that really illustrated the complexity of managing state in the browser. His response: “Like… all of them.”

Slide 17

Slide 17 text

He then proceeded to send me a list of cards, gave up, and said just search for “hard refresh”. We had come full circle, that the best way for us to deliver reliable data to our users was to refresh their browser when they came to critical pages. This is insanity.

Slide 18

Slide 18 text

In 2014, Shopify pulled the plug.

Slide 19

Slide 19 text

And have been pretty vocal against client-side MVC

Slide 20

Slide 20 text

T U R B O L I N K S 3 . 0 As David said, Shopify has been instrumental in building Turbolinks 3. We again followed in suite…

Slide 21

Slide 21 text

And all of our new apps have been written or re-written to stay squarely on the Rails golden path. We use Turbolinks and SJR a lot and love it.

Slide 22

Slide 22 text

And all of our new apps have been written or re-written to stay squarely on the Rails golden path. We use Turbolinks and SJR a lot and love it.

Slide 23

Slide 23 text

B U T W E W E R E S T I L L W R I T I N G A L O T O F J AVA S C R I P T But we were still writing a lot of JavaScript. SRJ is great when you interfaces justify an event that should be handled by a Rails controller. But they’re not so good for transient state or pages that are update (top- down) at a regular clip.

Slide 24

Slide 24 text

How de we write SRJ to handle transient state? This is merely to present helpful information to the user and doesn’t justify a Rails controller ping.

Slide 25

Slide 25 text

How de we write SRJ to handle transient state? This is merely to present helpful information to the user and doesn’t justify a Rails controller ping.

Slide 26

Slide 26 text

On the other extreme, we have pages that are completely real-time and interface with native apps through a single Rails API. We’d like for these to work consistently on nothing more than the data we are sending those devices. We want something that scales well to both of our needs, small and large.

Slide 27

Slide 27 text

We think React does this well.

Slide 28

Slide 28 text

C O M P O N E N T S Particularly the concept of components.

Slide 29

Slide 29 text

M Y G O A L My goal is two-fold.

Slide 30

Slide 30 text

MY GOAL T H AT Y O U C O U L D S H I P A R E A C T C O M P O N E N T I N A N H O U R First, that you could walk out of here and ship a React component in an hour. 5 minutes to setup. 55 minutes to write code.

Slide 31

Slide 31 text

STRETCH GOAL Y O U D O N ’ T M A K E T H E S A M E D U M B M I S TA K E S W E D I D Second, I’d like to see you avoid the mistakes made.

Slide 32

Slide 32 text

A R E A C T P R I M E R

Slide 33

Slide 33 text

J AVA S C R I P T L I B R A RY B Y FA C E B O O K

Slide 34

Slide 34 text

F O R C R E AT I N G U S E R I N T E R FA C E S

Slide 35

Slide 35 text

U S I N G C O M P O N E N T S * Component are the single building block of the React library.

Slide 36

Slide 36 text

C O M P O N E N T S D O 3 T H I N G S :

Slide 37

Slide 37 text

• R E N D E R • R E C E I V E P R O P S • M A I N TA I N S TAT E C O M P O N E N T S D O 3 T H I N G S : * render * receive props (these can be passed from component to component or a rails view) * maintain application state

Slide 38

Slide 38 text

T H I N K PA R T I A L W I T H L O C A L S You’d do pretty well to think of a React Component as a partial with locals

Slide 39

Slide 39 text

    <% @album.songs.each do |song| %>
  • <%= song.name %>
  • <% end %>
Imagine we run a album information service. We might have a block that shows the list of songs on an album.

Slide 40

Slide 40 text

    <% @album.songs.each do |song| %>
  • <%= song.name %>
  • <% end %>
These are the tracks from Taylor Swift’s 1989. I may or may not have completed this list from memory.

Slide 41

Slide 41 text

    <% @album.songs.each do |song| %>
  • <%= song.name %>
  • <% end %>
• Welcome To New York • Blank Space • Style • Out of the Woods • Shake It Off • I Wish You Would • Bad Blood • Wildest Dreams • How You Get the Girl • This Love • I know Places • Clean These are the tracks from Taylor Swift’s 1989. I may or may not have completed this list from memory.

Slide 42

Slide 42 text

    <% @album.songs.each do |song| %>
  • <%= song.name %>
  • <% end %>

Slide 43

Slide 43 text

    <% @album.songs.each do |song| %>
  • <%= song.name %>
  • <% end %>
Let’s make the simplest partial we can.

Slide 44

Slide 44 text

    <% @album.songs.each do |song| %>
  • <%= song.name %>
  • <% end %>
_songs.html.erb Create a new a file _songs.html.erb

Slide 45

Slide 45 text

    <% @album.songs.each do |song| %>
  • <%= song.name %>
  • <% end %>
_songs.html.erb move our view code in there.

Slide 46

Slide 46 text

<%= render "songs" %>
    <% @album.songs.each do |song| %>
  • <%= song.name %>
  • <% end %>
_songs.html.erb And finally render it into or original view. This is good but it’s not reusable.

Slide 47

Slide 47 text

<%= render "songs" %>
    <% @album.songs.each do |song| %>
  • <%= song.name %>
  • <% end %>
_songs.html.erb We’re coupled to the parent scope with an expectation of how songs can be retrieved.

Slide 48

Slide 48 text

<%= render "songs" %>
    <% songs .each do |song| %>
  • <%= song.name %>
  • <% end %>
_songs.html.erb This is where locals come in. If we expect a `songs` variable from the view, we can sidestep this coupling.

Slide 49

Slide 49 text

_songs.html.erb <%= render "songs" %>
    <% songs.each do |song| %>
  • <%= song.name %>
  • <% end %>
Now, back in our view…

Slide 50

Slide 50 text

<%= render partial: "songs", locals: { songs: @album.songs } %> _songs.html.erb
    <% songs.each do |song| %>
  • <%= song.name %>
  • <% end %>
We assign a local variable `songs`.

Slide 51

Slide 51 text

<%= render partial: "songs", locals: { songs: @album.songs } %> _songs.html.erb
    <% songs.each do |song| %>
  • <%= song.name %>
  • <% end %>
Now, if we want to use this partial on the index page, we can.

Slide 52

Slide 52 text

<%= render partial: "songs", locals: { songs: @album.songs } %> _songs.html.erb
    <% songs.each do |song| %>
  • <%= song.name %>
  • <% end %>
1989 • Welcome To New York • Blank Space • Style • Out of the Woods • Shake It Off • I Wish You Would • Bad Blood • Wildest Dreams • How You Get the Girl • This Love • I know Places • Clean RED • State Of Grace • Red • Treacherous • I Knew You Were Trouble • All Too Well • 22 • I Almost Do • We Are Never Ever Getting Back Together • Stay Stay Stay • The Last Time • … Speak Now • Mine • Sparks Fly • Back To December • Speak Now • Dear John • Mean • The Story Of Us • Never Grow Up • Enchanted • Better Than Revenge • Innocent • Haunted • Last Kiss Now, if we want to use this partial on the index page, we can.

Slide 53

Slide 53 text

<% @albums.each do |album| %> <% end %> index.html.erb <%= render partial: "songs", locals: { songs: @album.songs } %> We’ll iterate over the albums albums in our index.

Slide 54

Slide 54 text

<% @albums.each do |album| %>

<%= album.title %>

<%= render partial: "songs", locals: { songs: album.songs } %> <% end %> index.html.erb <%= render partial: "songs", locals: { songs: @album.songs } %> Add our title and render our songs partial, sending the songs in as locals.

Slide 55

Slide 55 text

<% @albums.each do |album| %>

<%= album.title %>

<%= render partial: "songs", locals: { songs: album.songs } %> <% end %> index.html.erb <%= render partial: "songs", locals: { songs: @album.songs } %> This works even though the context is different in each view.

Slide 56

Slide 56 text

Y O U K N O W T H I S

Slide 57

Slide 57 text

L E T ’ S D O I T I N J AVA S C R I P T W I T H R E A C T

Slide 58

Slide 58 text

<%= render partial: "songs", locals: { songs: @album.songs } %> <%= react_component: "Songs", { songs: @album.songs } %> Instead of render partial, we use the helper `react_component`

Slide 59

Slide 59 text

<%= render partial: "songs", locals: { songs: @album.songs } %> <%= react_component: "Songs", { songs: @album.songs } %> instead of the `songs` partial, we grab the `Songs` component.

Slide 60

Slide 60 text

<%= render partial: "songs", locals: { songs: @album.songs } %> <%= react_component: "Songs", { songs: @album.songs } %> _songs.html.erb `render partial` looks for a partial named `_songs.html.erb`

Slide 61

Slide 61 text

<%= render partial: "songs", locals: { songs: @album.songs } %> <%= react_component: "Songs", { songs: @album.songs } %> _songs.html.erb window.Songs Our react_component helper looks for a `Songs` component on `window`.

Slide 62

Slide 62 text

<%= render partial: "songs", locals: { songs: @album.songs } %> <%= react_component: "Songs", { songs: @album.songs } %> instead of sending in a `locals` object, we just send in a hash.

Slide 63

Slide 63 text

<%= render partial: "songs", locals: { songs: @album.songs } %> <%= react_component: "Songs", { songs: @album.songs } %> props React calls stores this object in a property named `props`.

Slide 64

Slide 64 text

P R O P S A R E I M M U TA B L E * kinda In Yehuda’s Rust talk, he described ownership is the right to modify or destroy. In React, a component does not own props. It only has access to see and use that value. JavaScript obviously has now way to protect these values but you should consider them to be immutable.

Slide 65

Slide 65 text

L E T ’ S D E F I N E A C O M P O N E N T

Slide 66

Slide 66 text

    <% songs.each do |song| %>
  • <%= song.name %>
  • <% end %>
var Songs We create a `Songs` variable…

Slide 67

Slide 67 text

    <% songs.each do |song| %>
  • <%= song.name %>
  • <% end %>
var Songs = React.createClass({}); render() { It’s a React class…

Slide 68

Slide 68 text

    <% songs.each do |song| %>
  • <%= song.name %>
  • <% end %>
var Songs = React.createClass({ render() { return ; } }); with a render method.

Slide 69

Slide 69 text

    <% songs.each do |song| %>
  • <%= song.name %>
  • <% end %>
var Songs = React.createClass({ render() { return
    ; } }); It returns an
      ,

    Slide 70

    Slide 70 text

      <% songs.each do |song| %>
    • <%= song.name %>
    • <% end %>
    var Songs = React.createClass({ render() { return
      {this.props.songs}
    ; } }); grabs the songs from the props object,

    Slide 71

    Slide 71 text

      <% songs.each do |song| %>
    • <%= song.name %>
    • <% end %>
    var Songs = React.createClass({ render() { return
      {this.props.songs.map()}
    ; } }); and iterates over it. in ruby we can just each over this list. in React, we need the array. so we map().

    Slide 72

    Slide 72 text

      <% songs.each do |song| %>
    • <%= song.name %>
    • <% end %>
    var Songs = React.createClass({ render() { var createItem = () => return
      {this.props.songs.map(createItem)}
    ; } }); for each iteration, we run a function to create a new song
  • .
  • Slide 73

    Slide 73 text

    var Songs = React.createClass({ render() { var createItem = (song) => ; return
      {this.props.songs.map(createItem)}
    ; } });
      <% songs.each do |song| %>
    • <%= song.name %>
    • <% end %>
    We take each song,

    Slide 74

    Slide 74 text

    var Songs = React.createClass({ render() { var createItem = (song) =>
  • {song.name}
  • ; return
      {this.props.songs.map(createItem)}
    ; } });
      <% songs.each do |song| %>
    • <%= song.name %>
    • <% end %>
    and return a
  • , which spits out the songs’ name.
  • Slide 75

    Slide 75 text

    L E T ’ S P R A C T I C E ! So, that’s our primer. You learned how to: * render components from rails views * define a component * and the similarities components have to Rails partials

    Slide 76

    Slide 76 text

    R E A L - T I M E C O M M E N T S F O R T H E 1 5 - M I N U T E B L O G I’d like to show you how to build out React components in practice. Let’s use an app that every Rails developer knows, the 15-minute blog.

    Slide 77

    Slide 77 text

    If you’ve never seen the 15 minute blog, this is what it looks like. This one copy is in Rails 4 but it looks identical to the first one built in 2003.

    Slide 78

    Slide 78 text

    <% @post.comments.each do |comment| %> <%= comment.body %>
    <% end %> Here’s the comments block.

    Slide 79

    Slide 79 text

    <% @post.comments.each do |comment| %> <%= comment.body %>
    <% end %> Let’s turn it directly into a React components.

    Slide 80

    Slide 80 text

    comment.js.jsx var Comment = React.createClass({ render() { return
    {this.props.comment}
    ; } }); <% @post.comments.each do |comment| %> <%= comment.body %>
    <% end %> We create a Comment variable,

    Slide 81

    Slide 81 text

    comment.js.jsx var Comment = React.createClass({ render() { return
    {this.props.comment}
    ; } }); <% @post.comments.each do |comment| %> <%= comment.body %>
    <% end %> it’s a react class,

    Slide 82

    Slide 82 text

    comment.js.jsx var Comment = React.createClass({ render() { return
    {this.props.comment}
    ; } }); <% @post.comments.each do |comment| %> <%= comment.body %>
    <% end %> with a render method.

    Slide 83

    Slide 83 text

    comment.js.jsx var Comment = React.createClass({ render() { return
    ; } }); <% @post.comments.each do |comment| %> <%= comment.body %>
    <% end %> it renders a
    ,

    Slide 84

    Slide 84 text

    comment.js.jsx var Comment = React.createClass({ render() { return

    ; } }); <% @post.comments.each do |comment| %> <%= comment.body %>
    <% end %> with an
    (because it’s 2003, remember),

    Slide 85

    Slide 85 text

    comment.js.jsx var Comment = React.createClass({ render() { return
    {this.props.comment}
    ; } }); <% @post.comments.each do |comment| %> <%= comment.body %>
    <% end %> and interpolates out the value of the comment prop.

    Slide 86

    Slide 86 text

    comment.js.jsx var Comment = React.createClass({ render() { return
    {this.props.comment}
    ; } }); <% @post.comments.each do |comment| %> <%= comment.body %>
    <% end %> Back in our view,

    Slide 87

    Slide 87 text

    comment.js.jsx var Comment = React.createClass({ render() { return
    {this.props.comment}
    ; } }); <% @post.comments.each do |comment| %> <% end %> we remove the template that draws out the comment,

    Slide 88

    Slide 88 text

    comment.js.jsx var Comment = React.createClass({ render() { return
    {this.props.comment}
    ; } }); <% @post.comments.each do |comment| %> <%= react_component "Comment", { comment: comment.body } %> <% end %> and use our react_component helper to render the component `Comment`.

    Slide 89

    Slide 89 text

    comment.js.jsx var Comment = React.createClass({ render() { return
    {this.props.comment}
    ; } }); <% @post.comments.each do |comment| %> <%= react_component "Comment", { comment: comment.body } %> <% end %> We dutifully provide our component a `comment` prop, which comes from the `comment` relationship on `@post`.

    Slide 90

    Slide 90 text

    Our blog transforms from this…

    Slide 91

    Slide 91 text

    to this.

    Slide 92

    Slide 92 text

    The astute reader will recognize that these are the same. But these comments are now rendered by JavaScript.

    Slide 93

    Slide 93 text

    The astute reader will recognize that these are the same. But these comments are now rendered by JavaScript.

    Slide 94

    Slide 94 text

    <% @post.comments.each do |comment| %> <%= react_component "Comment", { comment: comment.body } %> <% end %>

    Slide 95

    Slide 95 text

    <% @post.comments.each do |comment| %> <%= react_component "Comment", { comment: comment.body } %> <% end %> Let’s take it a step further and render the entire block as a React component.

    Slide 96

    Slide 96 text

    <% @post.comments.each do |comment| %> <%= react_component "Comment", { comment: comment.body } %> <% end %> comments.js.jsx var Comments = React.createClass({ render() { var createItem = ({body}) => ; return
      {this.props.comments.map(createItem)}
    ; } }); We create a `comments` variable,

    Slide 97

    Slide 97 text

    <% @post.comments.each do |comment| %> <%= react_component "Comment", { comment: comment.body } %> <% end %> comments.js.jsx var Comments = React.createClass({ render() { var createItem = ({body}) => ; return
      {this.props.comments.map(createItem)}
    ; } }); which is a React class,

    Slide 98

    Slide 98 text

    <% @post.comments.each do |comment| %> <%= react_component "Comment", { comment: comment.body } %> <% end %> comments.js.jsx var Comments = React.createClass({ render() { var createItem = ({body}) => ; return
      {this.props.comments.map(createItem)}
    ; } }); with a render method.

    Slide 99

    Slide 99 text

    <% @post.comments.each do |comment| %> <%= react_component "Comment", { comment: comment.body } %> <% end %> comments.js.jsx var Comments = React.createClass({ render() { var createItem = ({body}) => ; return
    ; } }); That render method returns a
    ,

    Slide 100

    Slide 100 text

    <% @post.comments.each do |comment| %> <%= react_component "Comment", { comment: comment.body } %> <% end %> comments.js.jsx var Comments = React.createClass({ render() { var createItem = ({body}) => ; return
    {this.props.comments}
    ; } }); and interpolates the value of `comments` from the `props` object.

    Slide 101

    Slide 101 text

    <% @post.comments.each do |comment| %> <%= react_component "Comment", { comment: comment.body } %> <% end %> comments.js.jsx var Comments = React.createClass({ render() { var createItem = ({body}) => ; return
    {this.props.comments.map()}
    ; } }); We iterate over each `comment` in `comments` with map(),

    Slide 102

    Slide 102 text

    comments.js.jsx var Comments = React.createClass({ render() { var createItem = () => ; return
    {this.props.comments.map(createItem)}
    ; } }); <% @post.comments.each do |comment| %> <%= react_component "Comment", { comment: comment.body } %> <% end %> and create an element for each comment,

    Slide 103

    Slide 103 text

    <% @post.comments.each do |comment| %> <%= react_component "Comment", { comment: comment.body } %> <% end %> comments.js.jsx var Comments = React.createClass({ render() { var createItem = ({body}) => ; return
    {this.props.comments.map(createItem)}
    ; } }); We take the comment, restructure out the `body` member (using es6 destructuring).

    Slide 104

    Slide 104 text

    <% @post.comments.each do |comment| %> <%= react_component "Comment", { comment: comment.body } %> <% end %> comments.js.jsx var Comments = React.createClass({ render() { var createItem = ({body}) => ; return
    {this.props.comments.map(createItem)}
    ; } }); And render the component for each. NOTE: When we render a component, from within a component, we can use this HTML-like syntax,

    Slide 105

    Slide 105 text

    <% @post.comments.each do |comment| %> <%= react_component "Comment", { comment: comment.body } %> <% end %> comments.js.jsx var Comments = React.createClass({ render() { var createItem = ({body}) => ; return
    {this.props.comments.map(createItem)}
    ; } }); and provide props as HTML-like attributes.

    Slide 106

    Slide 106 text

    <% @post.comments.each do |comment| %> <%= react_component "Comment", { comment: comment.body } %> <% end %> comments.js.jsx var Comments = React.createClass({ render() { var createItem = ({body}) => ; return
    {this.props.comments.map(createItem)}
    ; } }); We’ll give our component the comment boy as the comment.

    Slide 107

    Slide 107 text

    <% @post.comments.each do |comment| %> <%= react_component "Comment", { comment: comment.body } %> <% end %> comments.js.jsx var Comments = React.createClass({ render() { var createItem = ({body}) => ; return
    {this.props.comments.map(createItem)}
    ; } }); Back in our view,

    Slide 108

    Slide 108 text

    <%= react_component "Comment", { comment: comment.body } %> comments.js.jsx var Comments = React.createClass({ render() { var createItem = ({body}) => ; return
    {this.props.comments.map(createItem)}
    ; } }); we remove the iterator block,

    Slide 109

    Slide 109 text

    comments.js.jsx var Comments = React.createClass({ render() { var createItem = ({body}) => ; return
    {this.props.comments.map(createItem)}
    ; } }); <%= react_component "Comments", { comments: @post.comments } %> and update our helper to render the component. We then send it `@post.comments` as `comments`.

    Slide 110

    Slide 110 text

    Our blog transforms from this…

    Slide 111

    Slide 111 text

    to this. Again, the astute will observer will note that these are exactly the same.

    Slide 112

    Slide 112 text

    But now the entire comments block is rendered by React.

    Slide 113

    Slide 113 text

    But now the entire comments block is rendered by React.

    Slide 114

    Slide 114 text

    G E T T O T H E R E A L - T I M E B I T So far, all we’ve done is add code and split our runtime in two.

    Slide 115

    Slide 115 text

    < C o m m e n t s / > C O M P O N E N T We have a component that renders an array of comments.

    Slide 116

    Slide 116 text

    < C o m m e n t / > C O M P O N E N T We have a component that renders a single comment.

    Slide 117

    Slide 117 text

    < C o m m e n t s C o n t a i n e r / > C O M P O N E N T Now we’ll build a .

    Slide 118

    Slide 118 text

    • F E T C H C O M M E N T S • W H I C H C O M P O N E N T R E N D E R S T H E M < C o m m e n t s C o n t a i n e r / > C O M P O N E N T This component does two things. * It knows how to fetch new comments * It knows with comment is capable of rendering those comments

    Slide 119

    Slide 119 text

    T H I N K O F I T L I K E A R A I L S C O N T R O L L E R In those ways, it’s very similar to a Rails controller.

    Slide 120

    Slide 120 text

    <%= react_component "CommentsContainer", commentsPath: comments_post_path(@post) %> Our view now looks like this.

    Slide 121

    Slide 121 text

    <%= react_component "CommentsContainer", commentsPath: comments_post_path(@post) %> We’re rendering out the ,

    Slide 122

    Slide 122 text

    <%= react_component "CommentsContainer", commentsPath: comments_post_path(@post) %> and providing a JSON endpoint where our component can fetch new comments.

    Slide 123

    Slide 123 text

    var CommentsContainer = React.createClass({ componentWillMount() { this.fetchComments(); setInterval(this.fetchComments, 1000); }, fetchComments() { $.getJSON( this.props.commentsPath, (data) => this.setState({comments: data}); ); }, getInitialState() { return { comments: [] }; }, render() { return ; } });; and here’s what the component looks like.

    Slide 124

    Slide 124 text

    var CommentsContainer = React.createClass({ componentWillMount() { this.fetchComments(); setInterval(this.fetchComments, 1000); }, fetchComments() { $.getJSON( this.props.commentsPath, (data) => this.setState({comments: data}); ); }, getInitialState() { return { comments: [] }; }, render() { return ; } }); this half handles the fetching of new comments.

    Slide 125

    Slide 125 text

    var CommentsContainer = React.createClass({ componentWillMount() { this.fetchComments(); setInterval(this.fetchComments, 1000); }, fetchComments() { $.getJSON( this.props.commentsPath, (data) => this.setState({comments: data}); ); }, getInitialState() { return { comments: [] }; }, render() { return ; } }); This is our render method.

    Slide 126

    Slide 126 text

    var CommentsContainer = React.createClass({ componentWillMount() { this.fetchComments(); setInterval(this.fetchComments, 1000); }, fetchComments() { $.getJSON( this.props.commentsPath, (data) => this.setState({comments: data}); ); }, getInitialState() { return { comments: [] }; }, render() { return ; } }); And this function simply provides an initial starting value you. When we are doing async operations for data, it’s important to set starting data so you initial render doesn't spit undefined to the other components.

    Slide 127

    Slide 127 text

    var CommentsContainer = React.createClass({ componentWillMount() { this.fetchComments(); setInterval(this.fetchComments, 1000); }, fetchComments() { $.getJSON( this.props.commentsPath, (data) => this.setState({comments: data}); ); }, getInitialState() { return { comments: [] }; }, render() { return ; } }); These are the APIs we haven’t addressed yet.

    Slide 128

    Slide 128 text

    var CommentsContainer = React.createClass({ componentWillMount() { this.fetchComments(); setInterval(this.fetchComments, 1000); }, fetchComments() { $.getJSON( this.props.commentsPath, (data) => this.setState({comments: data}); ); }, getInitialState() { return { comments: [] }; }, render() { return ; } }); and these are the places that we interact with state.

    Slide 129

    Slide 129 text

    comments_container.js.jsx var CommentsContainer = React.createClass({}); We’ll create CommentsContainer, which is a React Class,

    Slide 130

    Slide 130 text

    comments_container.js.jsx var CommentsContainer = React.createClass({ render() { return ; } }); with a render method.

    Slide 131

    Slide 131 text

    comments_container.js.jsx var CommentsContainer = React.createClass({ render() { return ; } }); It renders our component,

    Slide 132

    Slide 132 text

    comments_container.js.jsx var CommentsContainer = React.createClass({ render() { return ; } }); with the value we provide in props. We’ll start with an empty array.

    Slide 133

    Slide 133 text

    comments_container.js.jsx var CommentsContainer = React.createClass({ render() { return ; } }); state This is our first interaction with state. Unlike `props`, `state` is data that our component owns and we plan to change over time.

    Slide 134

    Slide 134 text

    comments_container.js.jsx var CommentsContainer = React.createClass({ render() { return ; } }); There is a special `state` object that we will use.

    Slide 135

    Slide 135 text

    comments_container.js.jsx var CommentsContainer = React.createClass({ getInitialState() { }, render() { return ; } }); We’ll also take advantage of the lifecycle event `getInitialState`. When fetching asynchronous data, it’s important to provide our component with a safe initial-render state. We don’t want to send undefined through to our other components.

    Slide 136

    Slide 136 text

    comments_container.js.jsx var CommentsContainer = React.createClass({ getInitialState() { return { comments: [] }; }, render() { return ; } });

    Slide 137

    Slide 137 text

    comments_container.js.jsx var CommentsContainer = React.createClass({ getInitialState() { return { comments: [] }; }, render() { return ; } });

    Slide 138

    Slide 138 text

    comments_container.js.jsx var CommentsContainer = React.createClass({ fetchComments() { $.getJSON( this.props.commentsPath, (data) => this.setState({comments: data}); ); }, getInitialState() { return { comments: [] }; }, render() { return ; } This is our function for fetching comments from the server.

    Slide 139

    Slide 139 text

    comments_container.js.jsx var CommentsContainer = React.createClass({ fetchComments() { $.getJSON( this.props.commentsPath, (data) => this.setState({comments: data}); ); }, getInitialState() { return { comments: [] }; }, render() { return ; } It uses the `commentsPath` that we passed in through `props`.

    Slide 140

    Slide 140 text

    comments_container.js.jsx var CommentsContainer = React.createClass({ fetchComments() { $.getJSON( this.props.commentsPath, (data) => this.setState({comments: data}); ); }, getInitialState() { return { comments: [] }; }, render() { return ; } (here in the view)

    Slide 141

    Slide 141 text

    comments_container.js.jsx var CommentsContainer = React.createClass({ fetchComments() { $.getJSON( this.props.commentsPath, (data) => this.setState({comments: data}); ); }, getInitialState() { return { comments: [] }; }, render() { return ; } <%= react_component "CommentsContainer", commentsPath: comments_post_path(@post) %> (here in the view)

    Slide 142

    Slide 142 text

    comments_container.js.jsx var CommentsContainer = React.createClass({ fetchComments() { $.getJSON( this.props.commentsPath, (data) => this.setState({comments: data}); ); }, getInitialState() { return { comments: [] }; }, render() { return ; } (here in the view)

    Slide 143

    Slide 143 text

    comments_container.js.jsx var CommentsContainer = React.createClass({ fetchComments() { $.getJSON( this.props.commentsPath, (data) => this.setState({comments: data}); ); }, getInitialState() { return { comments: [] }; }, render() { return ; } on success, we take the data and call `setState` and apply the new `data` to `comments`.

    Slide 144

    Slide 144 text

    comments_container.js.jsx var CommentsContainer = React.createClass({ fetchComments() { $.getJSON( this.props.commentsPath, (data) => this.setState({comments: data}); ); }, getInitialState() { return { comments: [] }; }, render() { return ; } This function, is where the React magic happens. When we call this function, with new data, it’s going to re-render the entire component tree.

    Slide 145

    Slide 145 text

    comments_container.js.jsx var CommentsContainer = React.createClass({ componentWillMount() { this.fetchComments(); }, fetchComments() { $.getJSON( this.props.commentsPath, (data) => this.setState({comments: data}); ); }, getInitialState() { return { comments: [] }; }, When the component is ready to go, we want to fetch the current comments. We can use the lifecycle event `componentWillMount` do run this function when our component is instantiated.

    Slide 146

    Slide 146 text

    comments_container.js.jsx var CommentsContainer = React.createClass({ componentWillMount() { this.fetchComments(); setInterval(this.fetchComments, 1000); }, fetchComments() { $.getJSON( this.props.commentsPath, (data) => this.setState({comments: data}); ); }, getInitialState() { return { comments: [] }; We’ll tell it to fire again every second.

    Slide 147

    Slide 147 text

    Now we see the first change in our blog. Every time a comment is added, all other sessions will get the new comment.

    Slide 148

    Slide 148 text

    Now we see the first change in our blog. Every time a comment is added, all other sessions will get the new comment.

    Slide 149

    Slide 149 text

    I have two windows open here. One where I’m adding new comments through a standard Rails form and the second is polling for comment as they come in. As I mentioned early, calling setState with new data is triggers an re-render of the entire component tree. But that’s not what we’re seeing in the DOM. We’re only see single inserts for new comments. I haven’t told my component how to do this. I just told it what I want that group of comments to look. React only re-renders in memory. It then runs the minimal number of change operations no get the DOM up to date with the in-memory representation of our app.

    Slide 150

    Slide 150 text

    I have two windows open here. One where I’m adding new comments through a standard Rails form and the second is polling for comment as they come in. As I mentioned early, calling setState with new data is triggers an re-render of the entire component tree. But that’s not what we’re seeing in the DOM. We’re only see single inserts for new comments. I haven’t told my component how to do this. I just told it what I want that group of comments to look. React only re-renders in memory. It then runs the minimal number of change operations no get the DOM up to date with the in-memory representation of our app.

    Slide 151

    Slide 151 text

    S O H O W M U C H D O Y O U N E E D T O K N O W T O B E E F F E C T I V E ? I just ran you through 5 APIs pretty quick. You’re probably wondering how many APIs you need to know to be affective. Let’s look at the ones we’ve seen and a couple we don’t yet know.

    Slide 152

    Slide 152 text

    <%= react_component "Greeting" %> 1 This is how you render components into your Rails views.

    Slide 153

    Slide 153 text

    <%= react_component "Greeting", name: "Bob" %> 1 This is how you render components into your Rails views with props.

    Slide 154

    Slide 154 text

    var Greeting = React.createClass({ render() { return

    Hi {this.props.name}!

    ; } }); 2 This is how you define a component.

    Slide 155

    Slide 155 text

    var Greeting = React.createClass({ render() { return

    Hi {this.props.name}!

    ; } }); 3 This is how you use props in your render functions.

    Slide 156

    Slide 156 text

    propTypes 4 propTypes is a special object for tying into to React error system (in development).

    Slide 157

    Slide 157 text

    propTypes: { name: React.PropTypes.string } 4 For our greeting component, we can specify that `name` must be a string.

    Slide 158

    Slide 158 text

    propTypes: { name: React.PropTypes.string } 4 If we try to send it a number (or any other time), React will log out a warning telling you where the problem lies and how to fix it. This is a great way to communicate design expectations between team members.

    Slide 159

    Slide 159 text

    propTypes: { name: React.PropTypes.string } 4 If we try to send it a number (or any other time), React will log out a warning telling you where the problem lies and how to fix it. This is a great way to communicate design expectations between team members.

    Slide 160

    Slide 160 text

    getDefaultProps() 5 `getDefaultProps` is a lifecycle event for providing fallback props, when non are provided.

    Slide 161

    Slide 161 text

    getDefaultProps() { return { name: "guest" } } 5 Rubyists could think of this as `||=`. If no name is provided, we’ll use the name `guest`

    Slide 162

    Slide 162 text

    getDefaultProps() { return { name: "guest" } } Think of this as ||= in Ruby 5 Rubyists could think of this as `||=`. If no name is provided, we’ll use the name `guest`

    Slide 163

    Slide 163 text

    this.setState() 6 `setState` is the one tool you have for re-rendering a component tree. It’s like a big refresh button for the React-controlled pieces of your page.

    Slide 164

    Slide 164 text

    this.setState() The REFRESH button of React 6 `setState` is the one tool you have for re-rendering a component tree. It’s like a big refresh button for the React-controlled pieces of your page.

    Slide 165

    Slide 165 text

    getInitialState() { return { comments: [] } } 7 `getInitialState` is your ticket to setting up a default start state. This comes in particularly handy when dealing with asynchronous data.

    Slide 166

    Slide 166 text

    componentWillMount() 8 `componentWillMount` is a hook for running code as soon as the component is instantiated.

    Slide 167

    Slide 167 text

    componentWillMount() { $.getJSON('/comments.json', handleComments) } 8 It’s typically a good place for async events, such as fetching JSON.

    Slide 168

    Slide 168 text

    componentWillMount() componentWillUnmount() 9 It has a sibling event called `componentWillUnmount`.

    Slide 169

    Slide 169 text

    componentWillMount() { window.addEventListener('scroll', …); }, componentWillUnmount() { window.removeEventListener('scroll', …); } 9 You use these in tandem places where you add event listeners, you can clean them up.

    Slide 170

    Slide 170 text

    componentWillMount() { window.addEventListener('scroll', …); }, componentWillUnmount() { window.removeEventListener('scroll', …); } 9 Used for browser events,

    Slide 171

    Slide 171 text

    componentWillMount() { source.addEventListener('new-message', …); }, componentWillUnmount() { source.removeEventListener('new-message', …); } 9 Server-sent events,

    Slide 172

    Slide 172 text

    componentWillMount() { channel.bind('new-message', …); }, componentWillUnmount() { channel.bind('new-message', …); } 9 and services like Pusher.

    Slide 173

    Slide 173 text

    How many APIs 9 In total, that’s about 9 things you need to know to be effective. We used only those nine things to build Services Live.

    Slide 174

    Slide 174 text

    This entire app is re-rendering (in-memory) every second, but the only DOM element changing is the innerHTML of the clock. This is because react is ridiculously effective in providing the fewest number of DOM mutations required to bring it up to date.

    Slide 175

    Slide 175 text

    This entire app is re-rendering (in-memory) every second, but the only DOM element changing is the innerHTML of the clock. This is because react is ridiculously effective in providing the fewest number of DOM mutations required to bring it up to date.

    Slide 176

    Slide 176 text

    In this example, you can see the same thing with our chat client. Nothing is touched but the new s that are inserted with each new comment.

    Slide 177

    Slide 177 text

    In this example, you can see the same thing with our chat client. Nothing is touched but the new s that are inserted with each new comment.

    Slide 178

    Slide 178 text

    I M P L E M E N TAT I O N We have 6 applications and all of them used a different approach.

    Slide 179

    Slide 179 text

    R E A C T- R A I L S G E M * Provides the `react_component` helper * Uses UJS to correctly mount and unmount components * Turbolinks safe * Created by Facebook * Is now 1.0

    Slide 180

    Slide 180 text

    gem react-rails run rails g react:install R E A C T- R A I L S G E M * 2-step setup * does everything * even compiles production and development versions for the different environments

    Slide 181

    Slide 181 text

    rails g react:component Greeting R E A C T- R A I L S G E M Provides a simple generator for components

    Slide 182

    Slide 182 text

    var Greeting = React.createClass({ render() { return
    ; } }); R E A C T- R A I L S G E M The generated components look like this.

    Slide 183

    Slide 183 text

    rails g react:component Greeting name:string R E A C T- R A I L S G E M There are options for generating component with propTypes.

    Slide 184

    Slide 184 text

    var Greeting = React.createClass({ propTypes: { name: React.PropTypes.string }, render() { return (
    Name: {this.props.name}
    ); } }); R E A C T- R A I L S G E M Which look like this.

    Slide 185

    Slide 185 text

    R A I L S - A S S E T S react-assets is the easiest way to get JavaScript libraries into Rails

    Slide 186

    Slide 186 text

    gem 'sdoc', '~> 0.4.0', group: :doc group :development, :test do gem 'byebug' gem 'web-console', '~> 2.0' gem 'spring' end gem 'react-rails', '~> 1.0' source 'https://rails-assets.org' do gem 'rails-assets-alt' gem 'rails-assets-react-router' gem 'rails-assets-moment' end R A I L S - A S S E T S This is like magic. You * add rails-assets as a source * you prefix the bower libraries with `rails-assets-` and it will make first-class gems from those bower libraries. It’s terrific.

    Slide 187

    Slide 187 text

    MISTAKE! F I G H T I N G T H E A S S E T P I P E L I N E Our mistake was jumping on NPM too early. Adding browserify-rails and NPM can be slow and feels like it sidesteps The Rails Way.

    Slide 188

    Slide 188 text

    TODAY R E A C T- A S S E T S Use react-assets. It jives best with the Asset Pipeline.

    Slide 189

    Slide 189 text

    L A N G U A G E

    Slide 190

    Slide 190 text

    var Greeting = React.createClass({ propTypes: { name: React.PropTypes.string }, render() { return (
    Name: {this.props.name}
    ); } }); J S X So, this is JSX. It’s an XML-like syntax for writing templates that looks like HTML. People like to make a big deal out of it. It’s not a big deal. It works the same way ERB, Haml, and Handlebars do.

    Slide 191

    Slide 191 text

    var Greeting = React.createClass({ propTypes: { name: React.PropTypes.string }, render() { return React.createElement("div", null, React.createElement("div", null, "Name: " + {this.props.name} ); ); } }); J S X react-rails handles all of the transformations.

    Slide 192

    Slide 192 text

    var Greeting = React.createClass({ propTypes: { name: React.PropTypes.string }, render() { return (
    Name: {this.props.name}
    ); } }); J S X

    Slide 193

    Slide 193 text

    var Greeting = React.createClass({ propTypes: { name: React.PropTypes.string }, render() { return (
    Name: {this.props.name}
    ); } }); J AVA S C R I P T So this is a component in JavaScript.

    Slide 194

    Slide 194 text

    @Greeting = React.createClass propTypes: name: React.PropTypes.string render: -> `
    Name: {this.props.name}
    ` C O F F E E S C R I P T This is a component in CoffeeScript. You loose a few braces and parens.

    Slide 195

    Slide 195 text

    @Greeting = React.createClass propTypes: name: React.PropTypes.string render: -> `
    Name: {this.props.name}
    ` C O F F E E S C R I P T but we need to backtick escape our JSX

    Slide 196

    Slide 196 text

    @Greeting = React.createClass propTypes: name: React.PropTypes.string render: -> `
    Name: {this.props.name}
    ` C O F F E E S C R I P T We also have to use `this` instead of @ inside JSX * We spent 6 months building React in CoffeeScript and now we no longer do so. * React uses JSX to hide some of the implementation details of React. If you use JSX, version updates are trivial. If you use a third party library, you can be stuck waiting for new features, and find yourself taking the brunt of the. * For this reason, we didn’t really like the alternatives of CJSX or coffee-react. JSX THIS IS WHERE REACT HIDES IT’S IMPLEMENTATION DETAILS. The hardest updates we had were completely the fault of our early decision to sidestep JSX.

    Slide 197

    Slide 197 text

    E S 6 / E S 2 0 1 5 class Greeting extends React.Component { render() { return (
    {this.props.comment}
    ); } } Greeting.propTypes = { comment: React.PropTypes.string }; ES6. This is how we write all of our new components and how I’d recommend going forward. More and more react examples are being written in this way.

    Slide 198

    Slide 198 text

    E S 2 0 1 5 class Greeting extends React.Component { render() { return (
    {this.props.comment}
    ); } } Greeting.propTypes = { comment: React.PropTypes.string.isRequired }; The biggest difference is that you define your components as a class, extending `React.Componont`.

    Slide 199

    Slide 199 text

    E S 2 0 1 5 class Greeting extends React.Component { render() { return (
    {this.props.comment}
    ); } } Greeting.propTypes = { comment: React.PropTypes.string.isRequired }; propTypes are moved out of the definition, as a constructor property.

    Slide 200

    Slide 200 text

    E S 6 / E S 2 0 1 5 class Greeting extends React.Component { render() { return (
    {this.props.comment}
    ); } } Greeting.propTypes = { comment: React.PropTypes.string.isRequired }; Most everything else is the same.

    Slide 201

    Slide 201 text

    E S 6 / E S 2 0 1 5 class Greeting extends React.Component { getInitialState() { return {foo: "bar"} } render() { return (
    {this.props.comment}
    ); } } Greeting.propTypes = { But if you happen to find something that’s different,

    Slide 202

    Slide 202 text

    E S 6 / E S 2 0 1 5 class Greeting extends React.Component { getInitialState() { return {foo: "bar"} } render() { return (
    {this.props.comment}
    ); } } Greeting.propTypes = { React’s has terrific errors. You could seriously learn a thing or two about copywriting from this error.

    Slide 203

    Slide 203 text

    E S 6 / E S 2 0 1 5 class Greeting extends React.Component { getInitialState() { return {foo: "bar"} } render() { return (
    {this.props.comment}
    ); } } Greeting.propTypes = { React’s has terrific errors. You could seriously learn a thing or two about copywriting from this error.

    Slide 204

    Slide 204 text

    E S 6 / E S 2 0 1 5 class Greeting extends React.Component { constructor() { thas.state = {foo: "bar"} } render() { return (
    {this.props.comment}
    ); } } Greeting.propTypes = {

    Slide 205

    Slide 205 text

    MISTAKE! E A R LY O N W E B E T A G A I N S T J S X Our biggest mistake was betting against JSX. The most painful parts of our upgrades have been do to sidestepping JSX. JSX is where React hides its implementation details.

    Slide 206

    Slide 206 text

    TODAY U S E E S 6 / E S 2 0 1 5 + J S X Use es6/es2015 or whatever it’s called this week. Support in react-rails is partial, currently. But it will be improving shortly, as the library moves to using the babel transpiler.

    Slide 207

    Slide 207 text

    O N LY A S M U C H J AVA S C R I P T A S Y O U N E E D We’re very happy with React in providing us the ability to write only as much JavaScript as we need. I want to leave you with the shape your apps take with different approaches to JavaScript.

    Slide 208

    Slide 208 text

    This is your Rails app.

    Slide 209

    Slide 209 text

    This is your Rails app on sprinkles.

    Slide 210

    Slide 210 text

    This is your Rails app on Sprinkles, after 2 years.

    Slide 211

    Slide 211 text

    This is your app on client-side MVC.

    Slide 212

    Slide 212 text

    This is your Rails app on data-turbolinks-permanent. We’re using sprinkles to literally pin down ruby views on top of the cake.

    Slide 213

    Slide 213 text

    And this is the party we’re having with React.js on Rails.

    Slide 214

    Slide 214 text

    @ C H A N TA S T I C H I T M E W I T H Q U E S T I O N S I’d love to answer any questions.

    Slide 215

    Slide 215 text

    Q U E S T I O N S Here are the questions I remember answering.

    Slide 216

    Slide 216 text

    D O Y O U U S E F L U X ? I F S O , W H I C H L I B R A RY ?

    Slide 217

    Slide 217 text

    We do use flux in some of our apps. Those apps use the library Alt. It is incredibly documented, which is great for teams like ours. It’s available an Bower/rails-assets. We try very hard to use the container pattern with for our components. This makes it very easy to use shored components in isolation or inside of a framework like Alt.

    Slide 218

    Slide 218 text

    D O E S R E A C T- R A I L S A L S O A L L O W Y O U T O U S E J S M O D U L E S ?

    Slide 219

    Slide 219 text

    No. To use modules, you’ll need to use an integration like browserify-rails. Practically, browserify-rails dramatically slowed down our apps in development. We weren’t using the interoperability of js modules enough to justify the loss in development speed. We’re basically waitng for something better to come along.

    Slide 220

    Slide 220 text

    D O E S R E A C T H AV E S O M E T Y P E O F C O R R E S P O N D I N G T E S T F R A M E W O R K ?

    Slide 221

    Slide 221 text

    Jest, which is also written by Facebook. Jest is a pretty thin layer around Jasmine, which is great if you know Jasmine. The React library ships with testing utilities, which allows you to work with any testing tool you have comfort with.