Reactjs on Rails

Reactjs on Rails

An introductory talk to React.js for Rails developers. Covers high-level concepts, implementation, and languages. Includes all speaker notes and audience questions.

Ad9e927bce4bd0519631296b5af7af83?s=128

Michael Chan

April 24, 2015
Tweet

Transcript

  1. R E A C T. J S O N R

    A I L S
  2. 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.
  3. michael chan My name is Michael Chan

  4. @chantastic I’m @chantastic on twitter, where you’ll find a younger,

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

    happier version of me.
  6. I work on an app called Services. It helps churches

    organize: * volunteers * music * and services.
  7. Launched 2006 on Rails 1.13 It launched in 2006 on

    Rails 1.13
  8. So our approach to JavaScript has been: Our approach to

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

    Yes; write some.
  10. So, we know how sprinkles can turn…

  11. into mountains. We needed some structure.

  12. When we started our second app, in 2012, looked around

    to see what the bigger Rails shops were doing.
  13. Shopify was writing a JavaScript MVC called Batman.js to power

    their new real-time admin. And we followed suite.
  14. 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.
  15. 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.
  16. 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.”
  17. 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.
  18. In 2014, Shopify pulled the plug.

  19. And have been pretty vocal against client-side MVC

  20. 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…
  21. 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.
  22. 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.
  23. 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.
  24. 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.
  25. 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.
  26. 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.
  27. We think React does this well.

  28. C O M P O N E N T S

    Particularly the concept of components.
  29. M Y G O A L My goal is two-fold.

  30. 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.
  31. 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.
  32. A R E A C T P R I M

    E R
  33. J AVA S C R I P T L I

    B R A RY B Y FA C E B O O K
  34. F O R C R E AT I N G

    U S E R I N T E R FA C E S
  35. 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.
  36. C O M P O N E N T S

    D O 3 T H I N G S :
  37. • 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
  38. 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
  39. <ul> <% @album.songs.each do |song| %> <li><%= song.name %></li> <%

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

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

    end %> </ul> • 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.
  42. <ul> <% @album.songs.each do |song| %> <li><%= song.name %></li> <%

    end %> </ul>
  43. <ul> <% @album.songs.each do |song| %> <li><%= song.name %></li> <%

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

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

    end %> </ul> _songs.html.erb move our view code in there.
  46. <%= render "songs" %> <ul> <% @album.songs.each do |song| %>

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

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

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

    %> <li><%= song.name %></li> <% end %> </ul> Now, back in our view…
  50. <%= render partial: "songs", locals: { songs: @album.songs } %>

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

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

    _songs.html.erb <ul> <% songs.each do |song| %> <li><%= song.name %></li> <% end %> </ul> 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.
  53. <% @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.
  54. <% @albums.each do |album| %> <h2><%= album.title %></h2> <%= 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.
  55. <% @albums.each do |album| %> <h2><%= album.title %></h2> <%= 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.
  56. Y O U K N O W T H I

    S
  57. 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
  58. <%= render partial: "songs", locals: { songs: @album.songs } %>

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

    <%= react_component: "Songs", { songs: @album.songs } %> instead of the `songs` partial, we grab the `Songs` component.
  60. <%= 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`
  61. <%= 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`.
  62. <%= 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.
  63. <%= render partial: "songs", locals: { songs: @album.songs } %>

    <%= react_component: "Songs", { songs: @album.songs } %> props React calls stores this object in a property named `props`.
  64. 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.
  65. L E T ’ S D E F I N

    E A C O M P O N E N T
  66. <ul> <% songs.each do |song| %> <li><%= song.name %></li> <%

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

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

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

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

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

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

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

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

    => <li>{song.name}</li>; return <ul>{this.props.songs.map(createItem)}</ul>; } }); <ul> <% songs.each do |song| %> <li><%= song.name %></li> <% end %> </ul> and return a <li />, which spits out the songs’ name.
  75. 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
  76. 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.
  77. 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.
  78. <% @post.comments.each do |comment| %> <%= comment.body %> <hr />

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

    <% end %> Let’s turn it directly into a React components.
  80. comment.js.jsx var Comment = React.createClass({ render() { return <div>{this.props.comment}<hr/></div>; }

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

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

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

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

    }); <% @post.comments.each do |comment| %> <%= comment.body %> <hr /> <% end %> with an <hr /> (because it’s 2003, remember),
  85. comment.js.jsx var Comment = React.createClass({ render() { return <div>{this.props.comment}<hr/></div>; }

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

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

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

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

    }); <% @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`.
  90. Our blog transforms from this…

  91. to this.

  92. The astute reader will recognize that these are the same.

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

    But these comments are now rendered by JavaScript.
  94. <% @post.comments.each do |comment| %> <%= react_component "Comment", { comment:

    comment.body } %> <% end %>
  95. <% @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.
  96. <% @post.comments.each do |comment| %> <%= react_component "Comment", { comment:

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

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

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

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

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

    comment.body } %> <% end %> comments.js.jsx var Comments = React.createClass({ render() { var createItem = ({body}) => <Comment comment={body} />; return <div>{this.props.comments.map()}</div>; } }); We iterate over each `comment` in `comments` with map(),
  102. comments.js.jsx var Comments = React.createClass({ render() { var createItem =

    () => <Comment comment={body} />; return <div>{this.props.comments.map(createItem)}</div>; } }); <% @post.comments.each do |comment| %> <%= react_component "Comment", { comment: comment.body } %> <% end %> and create an element for each comment,
  103. <% @post.comments.each do |comment| %> <%= react_component "Comment", { comment:

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

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

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

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

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

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

    ({body}) => <Comment comment={body} />; return <div>{this.props.comments.map(createItem)}</div>; } }); <%= react_component "Comments", { comments: @post.comments } %> and update our helper to render the <comments /> component. We then send it `@post.comments` as `comments`.
  110. Our blog transforms from this…

  111. to this. Again, the astute will observer will note that

    these are exactly the same.
  112. But now the entire comments block is rendered by React.

  113. But now the entire comments block is rendered by React.

  114. 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.
  115. < C o m m e n t s /

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

    C O M P O N E N T We have a <Comment /> component that renders a single comment.
  117. < 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 <CommentsContainer />.
  118. • 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
  119. 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.
  120. <%= react_component "CommentsContainer", commentsPath: comments_post_path(@post) %> Our view now looks

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

    <CommentsContainer />,
  122. <%= react_component "CommentsContainer", commentsPath: comments_post_path(@post) %> and providing a JSON

    endpoint where our component can fetch new comments.
  123. 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 <Comments comments={this.state.comments} />; } });; and here’s what the component looks like.
  124. 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 <Comments comments={this.state.comments} />; } }); this half handles the fetching of new comments.
  125. 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 <Comments comments={this.state.comments} />; } }); This is our render method.
  126. 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 <Comments comments={this.state.comments} />; } }); 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.
  127. 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 <Comments comments={this.state.comments} />; } }); These are the APIs we haven’t addressed yet.
  128. 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 <Comments comments={this.state.comments} />; } }); and these are the places that we interact with state.
  129. comments_container.js.jsx var CommentsContainer = React.createClass({}); We’ll create CommentsContainer, which is

    a React Class,
  130. comments_container.js.jsx var CommentsContainer = React.createClass({ render() { return ; }

    }); with a render method.
  131. comments_container.js.jsx var CommentsContainer = React.createClass({ render() { return <Comments />;

    } }); It renders our <Comments /> component,
  132. comments_container.js.jsx var CommentsContainer = React.createClass({ render() { return <Comments comments={[]}

    />; } }); with the value we provide in props. We’ll start with an empty array.
  133. comments_container.js.jsx var CommentsContainer = React.createClass({ render() { return <Comments comments={[]}

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

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

    return <Comments comments={this.state.comments} />; } }); 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.
  136. comments_container.js.jsx var CommentsContainer = React.createClass({ getInitialState() { return { comments:

    [] }; }, render() { return <Comments comments={this.state.comments} />; } });
  137. comments_container.js.jsx var CommentsContainer = React.createClass({ getInitialState() { return { comments:

    [] }; }, render() { return <Comments comments={this.state.comments} />; } });
  138. comments_container.js.jsx var CommentsContainer = React.createClass({ fetchComments() { $.getJSON( this.props.commentsPath, (data)

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

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

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

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

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

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

    => this.setState({comments: data}); ); }, getInitialState() { return { comments: [] }; }, render() { return <Comments comments={this.state.comments} />; } 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.
  145. 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.
  146. 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.
  147. Now we see the first change in our blog. Every

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

    time a comment is added, all other sessions will get the new comment.
  149. 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.
  150. 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.
  151. 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.
  152. <%= react_component "Greeting" %> 1 This is how you render

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

    you render components into your Rails views with props.
  154. var Greeting = React.createClass({ render() { return <h2>Hi {this.props.name}!</h2>; }

    }); 2 This is how you define a component.
  155. var Greeting = React.createClass({ render() { return <h2>Hi {this.props.name}!</h2>; }

    }); 3 This is how you use props in your render functions.
  156. propTypes 4 propTypes is a special object for tying into

    to React error system (in development).
  157. propTypes: { name: React.PropTypes.string } 4 For our greeting component,

    we can specify that `name` must be a string.
  158. 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.
  159. 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.
  160. getDefaultProps() 5 `getDefaultProps` is a lifecycle event for providing fallback

    props, when non are provided.
  161. getDefaultProps() { return { name: "guest" } } 5 Rubyists

    could think of this as `||=`. If no name is provided, we’ll use the name `guest`
  162. 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`
  163. 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.
  164. 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.
  165. 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.
  166. componentWillMount() 8 `componentWillMount` is a hook for running code as

    soon as the component is instantiated.
  167. componentWillMount() { $.getJSON('/comments.json', handleComments) } 8 It’s typically a good

    place for async events, such as fetching JSON.
  168. componentWillMount() componentWillUnmount() 9 It has a sibling event called `componentWillUnmount`.

  169. 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.
  170. componentWillMount() { window.addEventListener('scroll', …); }, componentWillUnmount() { window.removeEventListener('scroll', …); }

    9 Used for browser events,
  171. componentWillMount() { source.addEventListener('new-message', …); }, componentWillUnmount() { source.removeEventListener('new-message', …); }

    9 Server-sent events,
  172. componentWillMount() { channel.bind('new-message', …); }, componentWillUnmount() { channel.bind('new-message', …); }

    9 and services like Pusher.
  173. 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.
  174. 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.
  175. 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.
  176. In this example, you can see the same thing with

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

    our chat client. Nothing is touched but the new <tr />s that are inserted with each new comment.
  178. I M P L E M E N TAT I

    O N We have 6 applications and all of them used a different approach.
  179. 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
  180. 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
  181. rails g react:component Greeting R E A C T- R

    A I L S G E M Provides a simple generator for components
  182. var Greeting = React.createClass({ render() { return <div />; }

    }); R E A C T- R A I L S G E M The generated components look like this.
  183. 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.
  184. var Greeting = React.createClass({ propTypes: { name: React.PropTypes.string }, render()

    { return ( <div> <div>Name: {this.props.name}</div> </div> ); } }); R E A C T- R A I L S G E M Which look like this.
  185. R A I L S - A S S E

    T S react-assets is the easiest way to get JavaScript libraries into Rails
  186. 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.
  187. 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.
  188. TODAY R E A C T- A S S E

    T S Use react-assets. It jives best with the Asset Pipeline.
  189. L A N G U A G E

  190. var Greeting = React.createClass({ propTypes: { name: React.PropTypes.string }, render()

    { return ( <div> <div>Name: {this.props.name}</div> </div> ); } }); 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.
  191. 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.
  192. var Greeting = React.createClass({ propTypes: { name: React.PropTypes.string }, render()

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

    { return ( <div> <div>Name: {this.props.name}</div> </div> ); } }); J AVA S C R I P T So this is a component in JavaScript.
  194. @Greeting = React.createClass propTypes: name: React.PropTypes.string render: -> ` <div>

    <div>Name: {this.props.name}</div> </div> ` 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.
  195. @Greeting = React.createClass propTypes: name: React.PropTypes.string render: -> ` <div>

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

    <div>Name: {this.props.name}</div> </div> ` 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.
  197. E S 6 / E S 2 0 1 5

    class Greeting extends React.Component { render() { return ( <div> <div>{this.props.comment}</div> </div> ); } } 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.
  198. E S 2 0 1 5 class Greeting extends React.Component

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

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

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

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

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

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

    class Greeting extends React.Component { constructor() { thas.state = {foo: "bar"} } render() { return ( <div> <div>{this.props.comment}</div> </div> ); } } Greeting.propTypes = {
  205. 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.
  206. 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.
  207. 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.
  208. This is your Rails app.

  209. This is your Rails app on sprinkles.

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

  211. This is your app on client-side MVC.

  212. This is your Rails app on data-turbolinks-permanent. We’re using sprinkles

    to literally pin down ruby views on top of the cake.
  213. And this is the party we’re having with React.js on

    Rails.
  214. @ 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.
  215. Q U E S T I O N S Here

    are the questions I remember answering.
  216. 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 ?
  217. 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.
  218. 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 ?
  219. 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.
  220. 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 ?
  221. 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.