Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Reactjs on Rails (slides only)

Reactjs on Rails (slides only)

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

Michael Chan

April 24, 2015
Tweet

More Decks by Michael Chan

Other Decks in Programming

Transcript

  1. 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
  2. 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
  3. 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
  4. J AVA S C R I P T L I

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

    U S E R I N T E R FA C E S
  6. C O M P O N E N T S

    D O 3 T H I N G S :
  7. • 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 :
  8. T H I N K PA R T I A

    L W I T H L O C A L S
  9. <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
  10. <%= render "songs" %> <ul> <% @album.songs.each do |song| %>

    <li><%= song.name %></li> <% end %> </ul> _songs.html.erb
  11. <%= render "songs" %> <ul> <% @album.songs.each do |song| %>

    <li><%= song.name %></li> <% end %> </ul> _songs.html.erb
  12. <%= render "songs" %> <ul> <% songs .each do |song|

    %> <li><%= song.name %></li> <% end %> </ul> _songs.html.erb
  13. _songs.html.erb <%= render "songs" %> <ul> <% songs.each do |song|

    %> <li><%= song.name %></li> <% end %> </ul>
  14. <%= render partial: "songs", locals: { songs: @album.songs } %>

    _songs.html.erb <ul> <% songs.each do |song| %> <li><%= song.name %></li> <% end %> </ul>
  15. <%= render partial: "songs", locals: { songs: @album.songs } %>

    _songs.html.erb <ul> <% songs.each do |song| %> <li><%= song.name %></li> <% end %> </ul>
  16. <%= 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
  17. <% @albums.each do |album| %> <% end %> index.html.erb <%=

    render partial: "songs", locals: { songs: @album.songs } %>
  18. <% @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 } %>
  19. <% @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 } %>
  20. 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
  21. <%= render partial: "songs", locals: { songs: @album.songs } %>

    <%= react_component: "Songs", { songs: @album.songs } %>
  22. <%= render partial: "songs", locals: { songs: @album.songs } %>

    <%= react_component: "Songs", { songs: @album.songs } %>
  23. <%= render partial: "songs", locals: { songs: @album.songs } %>

    <%= react_component: "Songs", { songs: @album.songs } %> _songs.html.erb
  24. <%= render partial: "songs", locals: { songs: @album.songs } %>

    <%= react_component: "Songs", { songs: @album.songs } %> _songs.html.erb window.Songs
  25. <%= render partial: "songs", locals: { songs: @album.songs } %>

    <%= react_component: "Songs", { songs: @album.songs } %>
  26. <%= render partial: "songs", locals: { songs: @album.songs } %>

    <%= react_component: "Songs", { songs: @album.songs } %> props
  27. P R O P S A R E I M

    M U TA B L E * kinda
  28. L E T ’ S D E F I N

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

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

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

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

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

    end %> </ul> var Songs = React.createClass({ render() { return <ul>{this.props.songs.map()}</ul>; } });
  34. <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>; } });
  35. 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>
  36. 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>
  37. 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
  38. comment.js.jsx var Comment = React.createClass({ render() { return <div>{this.props.comment}<hr/></div>; }

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

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

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

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

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

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

    }); <% @post.comments.each do |comment| %> <%= comment.body %> <hr /> <% end %>
  45. 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 %>
  46. 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 %>
  47. <% @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>; } });
  48. <% @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>; } });
  49. <% @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>; } });
  50. <% @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>; } });
  51. <% @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>; } });
  52. <% @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>; } });
  53. 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 %>
  54. <% @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>; } });
  55. <% @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>; } });
  56. <% @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>; } });
  57. <% @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>; } });
  58. <% @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>; } });
  59. <%= 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>; } });
  60. 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 } %>
  61. G E T T O T H E R E

    A L - T I M E B I T
  62. < C o m m e n t s /

    > C O M P O N E N T
  63. < C o m m e n t / >

    C O M P O N E N T
  64. < 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
  65. • 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
  66. 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
  67. 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} />; } });;
  68. 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} />; } });
  69. 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} />; } });
  70. 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} />; } });
  71. 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} />; } });
  72. 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} />; } });
  73. comments_container.js.jsx var CommentsContainer = React.createClass({ getInitialState() { return { comments:

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

    [] }; }, render() { return <Comments comments={this.state.comments} />; } });
  75. 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} />;
  76. 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} />;
  77. 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} />;
  78. 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) %>
  79. 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} />;
  80. 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} />;
  81. 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} />;
  82. comments_container.js.jsx var CommentsContainer = React.createClass({ componentWillMount() { this.fetchComments(); }, fetchComments()

    { $.getJSON( this.props.commentsPath, (data) => this.setState({comments: data}); ); }, getInitialState() { return { comments: [] };
  83. 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() {
  84. 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 ?
  85. 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
  86. 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
  87. 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
  88. var Greeting = React.createClass({ propTypes: { name: React.PropTypes.string }, render()

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

    { return ( <div> <div>Name: {this.props.name}</div> </div> ); } }); J S X
  91. 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
  92. @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
  93. @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
  94. @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
  95. 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 };
  96. 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 };
  97. 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 };
  98. 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 };
  99. 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> ); } }
  100. 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> ); } }
  101. 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> ); } }
  102. 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> ); } }
  103. MISTAKE! E A R LY O N W E B

    E T A G A I N S T J S X
  104. TODAY U S E E S 6 / E S

    2 0 1 5 + J S X
  105. 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
  106. @ 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
  107. 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 ?
  108. 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.
  109. 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 ?
  110. 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.
  111. 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 ?
  112. 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.