React Tips & Tricks

React Tips & Tricks

Some things, I learned during an React migration, we are doing at ProductHunt.

7a0e72a6f55811246bb5d9a946fd2e49?s=128

Radoslav Stankov

May 20, 2015
Tweet

Transcript

  1. 3.
  2. 4.
  3. 6.

    app.component('Graph', function() { elements: { legend: '.legend', graph: '.graph' },

    wasInitialized: function() { # ... using some js graph lib } }
  4. 8.

    app.component('InfiniteScrolling', function() { elements: { marker: '[data-scroll-marker]' }, events: {

    'scroll:bottom': 'loadMore' }, wasInitialized: function() { // ... code code }, wasRemoved: function() { // ... be good and clean after your self } });
  5. 9.
  6. 10.
  7. 11.
  8. 12.

    Search Field Location Search Bar Search Model Search Result: Products

    Search Result: Collections Search Result: Users Search Index: Users Search Index: Products Search Index: Collections
  9. 13.
  10. 14.

    • I don’t want to develop my framework • I

    want module system • I want better "View" layer • I want structure and rules • I want a stable framework • I want to get rid of html mess
  11. 15.
  12. 16.
  13. 17.
  14. 18.
  15. 19.
  16. 20.
  17. 21.
  18. 22.

    • Close to what already had • Really easy server

    side rendering • Plays great with ES6 • Virtual DOM • JSX • Data direction • Flux
  19. 23.
  20. 24.
  21. 25.
  22. 26.
  23. 27.

    <div className="post-list"> <article className="post-item"> <button className="vote-button">{post.votesCount}</button> <div> <h1>{post.name}</h1> <p>{post.headline}</p> </div>

    <button className="comment-button">{post.commentsCount}</button> </article> <article className="post-item"> <button className="vote-button">{post.votesCount}</button> <div> <h1>{post.name}</h1> <p>{post.headline}</p> </div> <button className="comment-button">{post.commentsCount}</button> </article> <!—- more posts —-> </div>
  24. 28.

    var PostList = React.createClass({ render: function() { var posts =

    this.props.posts.map(function(post) { return ( React.createElement("article", {className: "post-item"}, React.createElement("button", {className: "vote-button"}, post.votesCount), React.createElement("div", null, React.createElement("h1", null, post.name), React.createElement("p", null, post.headline) ), React.createElement("button", {className: "comment-button"}, post.commentsCount) ) ); }); return ( React.createElement("div", {className: "post-list"}, posts) ) } });
  25. 30.

    var PostList = React.createClass({ render: function() { var posts =

    this.props.posts.map(function(post) { return ( <article className="post-item"> <button className="vote-button">{post.votesCount}</button> <div> <h1>{post.name}</h1> <p>{post.headline}</p> </div> <button className="comment-button">{post.commentsCount}</button> </article> ); }); return ( <div className="post-list"> {posts} </div> ) } });
  26. 34.

    var PostList = React.createClass({ render: function() { var posts =

    this.props.posts.map(function(post) { return ( <article className="post-item"> <button className="vote-button">{post.votesCount}</button> <div> <h1>{post.name}</h1> <p>{post.headline}</p> </div> <button className="comment-button">{post.commentsCount}</button> </article> ); }); return ( <div className="post-list"> {posts} </div> ) } });
  27. 35.
  28. 36.

    var PostList = React.createClass({ render: function() { var posts =

    this.props.posts.map(function(post) { return ( <article className="post-item"> <button className="vote-button">{post.votesCount}</button> <div> <h1>{post.name}</h1> <p>{post.headline}</p> </div> <button className="comment-button">{post.commentsCount}</button> </article> ); }); return ( <div className="post-list"> {posts} </div> ) } });
  29. 37.

    var PostList = React.createClass({ render: function() { return ( <div

    className="post-list"> {this.renderItems()} </div> ) }, renderItems: function () { return this.props.posts.map(function(post) { return ( <article className="post-item" key={'post-' + post.id}> <button className="vote-button">{post.votesCount}</button> <div> <h1>{post.name}</h1> <p>{post.headline}</p> </div> <button className="comment-button">{post.commentsCount}</button> </article> ); }); } });
  30. 39.

    var PostList = React.createClass({ render: function() { return ( <div

    className="post-list"> {this.renderItems()} </div> ) }, renderItems: function () { return this.props.posts.map(function(post) { return ( <article className="post-item" key={'post-' + post.id}> <button className="vote-button">{post.votesCount}</button> <div> <h1>{post.name}</h1> <p>{post.headline}</p> </div> <button className="comment-button">{post.commentsCount}</button> </article> ); }); } });
  31. 40.

    var PostList = React.createClass({ propTypes: { posts: React.PropTypes.array.isRequired }, getDefaultProps:

    function() { return { posts: [] }; }, render: function() { return ( <div className="post-list"> {this.renderItems()} </div> ) }, renderItems: function () { return this.props.posts.map(function(post) { return ( <article className="post-item" key={'post-' + post.id}> <button className="vote-button">{post.votesCount}</button> <div> <h1>{post.name}</h1> <p>{post.headline}</p> </div> <button className="comment-button">{post.commentsCount}</button> </article> ); }); } });
  32. 41.

    Prop validation React.createClass({ propTypes: { optionalArray: React.PropTypes.array, optionalBool: React.PropTypes.bool, optionalFunc:

    React.PropTypes.func, optionalNumber: React.PropTypes.number, optionalObject: React.PropTypes.object, optionalString: React.PropTypes.string, requiredString: React.PropTypes.string.isRequired, requiredObject: React.PropTypes.shape({ optionalString: React.PropTypes.string, requiredString: React.PropTypes.string.isRequired, }) // more information: // https://facebook.github.io/react/docs/reusable-components.html });
  33. 42.

    var PostList = React.createClass({ render: function() { return ( <div

    className="post-list"> {this.renderItems()} </div> ) }, renderItems: function () { return this.props.posts.map(function(post) { return ( <article className="post-item" key={'post-' + post.id}> <button className="vote-button">{post.votesCount}</button> <div> <h1>{post.name}</h1> <p>{post.headline}</p> </div> <button className="comment-button">{post.commentsCount}</button> </article> ); }); } });
  34. 43.
  35. 44.
  36. 45.

    Components React is all about building reusable components. In fact,

    with React the only thing you do is build components. Since they're so encapsulated, components make code reuse, testing, and separation of concerns easy.
  37. 46.

    var PostList = React.createClass({ render: function() { return ( <div

    className="post-list"> {this.renderItems()} </div> ) }, renderItems: function () { return this.props.posts.map(function(post) { return <PostItem key={'post-' + post.id} {...post} /> }); } });
  38. 47.

    var PostItem = React.createClass({ render: function() { return ( <article

    className="post-item"> <button className="vote-button">{this.props.votesCount}</button> <div> <h1>{this.props.name}</h1> <p>{this.props.headline}</p> </div> <button className="comment-button">{this.props.commentsCount}</button> </article> ); } });
  39. 48.

    var PostItem = React.createClass({ propTypes: { votesCount: React.PropTypes.number.isRequired, commentsCount: React.PropTypes.number.isRequired,

    name: React.PropTypes.string.isRequired, headline: React.PropTypes.string.isRequired }, // code ... });
  40. 49.
  41. 50.

    var PostItem = React.createClass({ getInitialState: function() { return { hasVoted:

    Votes.hasVoted(this.props.id) }; }, render: function() { var className = classNames('vote-button', {active: this.state.hasVoted}); return ( <article className="post-item"> <button className={className}>{this.props.votesCount}</button> <div> <h1>{this.props.name}</h1> <p>{this.props.headline}</p> </div> <button className="comment-button">{this.props.commentsCount}</button> </article> ); } });
  42. 52.
  43. 53.

    var PostItem = React.createClass({ getInitialState: function() { return { hasVoted:

    Votes.hasVoted(this.props.id), votesCount: this.props.votesCount, }; }, render: function() { var className = classNames('vote-button', {active: this.state.hasVoted}); return ( <article className="post-item"> <button className={className} onClick={this.handleVoteClick}>{this.state.votesCount}</button> <div> <h1>{this.props.name}</h1> <p>{this.props.headline}</p> </div> <button className="comment-button">{this.props.commentsCount}</button> </article> ); }, handleVoteClick: function () { var voted = Votes.toggleVote(this.props.id); this.setState({ hasVoted: voted, votesCount: this.state.votesCount + (voted ? 1 : -1) }); } });
  44. 55.

    var PostItem = React.createClass({ getInitialState: function() { return { hasVoted:

    Votes.hasVoted(this.props.id), votesCount: this.props.votesCount }; }, componentDidMount: function() { Votes.onVoteChange(this.props.id, this.handleVotesUpdate); }, componentWillUnmount: function () { Votes.offVoteChange(this.props.id, this.handleVotesUpdate); }, render: function() { var className = classNames('vote-button', {active: this.state.hasVoted}); return ( <article className="post-item"> <button className={className} onClick={this.handleVoteClick}>{this.state.votesCount}</button> <div> <h1>{this.props.name}</h1> <p>{this.props.headline}</p> </div> <button className="comment-button">{this.state.commentsCount}</button> </article> ); }, handleVotesUpdate: function(count) { this.setState({ hasVoted: Votes.hasVoted(this.props.id), votesCount: count });
  45. 56.

    Votes.onVoteChange(this.props.id, this.handleVotesUpdate); }, componentWillUnmount: function () { Votes.offVoteChange(this.props.id, this.handleVotesUpdate); },

    render: function() { var className = classNames('vote-button', {active: this.state.hasVoted}); return ( <article className="post-item"> <button className={className} onClick={this.handleVoteClick}>{this.state.votesCount}</button> <div> <h1>{this.props.name}</h1> <p>{this.props.headline}</p> </div> <button className="comment-button">{this.state.commentsCount}</button> </article> ); }, handleVotesUpdate: function(count) { this.setState({ hasVoted: Votes.hasVoted(this.props.id), votesCount: count }); }, handleVoteClick: function () { Votes.toggleVote(this.props.id); } });
  46. 57.

    var PostItem = React.createClass({ getInitialState: function() { return { hasVoted:

    Votes.hasVoted(this.props.id), votesCount: this.props.votesCount, commentsCount: this.props.commentsCount }; }, componentDidMount: function() { Comment.observe(this.props.id, this.handleCommentsUpdate); Votes.onVoteChange(this.props.id, this.handleVotesUpdate); }, componentWillUnmount: function () { Comment.stopObserving(this.props.id, this.handleCommentsUpdate); Votes.offVoteChange(this.props.id, this.handleVotesUpdate); }, render: function() { var className = classNames('vote-button', {active: this.state.hasVoted}); return ( <article className="post-item"> <button className={className} onClick={this.handleVoteClick}>{this.state.votesCount}</button> <div> <h1>{this.props.name}</h1> <p>{this.props.headline}</p> </div> <button className="comment-button">{this.state.commentsCount}</button> </article> ); }, handleCommentsUpdate: function (count) { this.setState({
  47. 58.

    render: function() { var className = classNames('vote-button', {active: this.state.hasVoted}); return

    ( <article className="post-item"> <button className={className} onClick={this.handleVoteClick}>{this.state.votesCount}</button> <div> <h1>{this.props.name}</h1> <p>{this.props.headline}</p> </div> <button className="comment-button">{this.state.commentsCount}</button> </article> ); }, handleCommentsUpdate: function (count) { this.setState({ commentsCount: count }); }, handleVotesUpdate: function(count) { this.setState({ hasVoted: Votes.hasVoted(this.props.id), votesCount: count }); }, handleVoteClick: function () { var voted = Votes.toggleVote(this.props.id); } });
  48. 65.

    Virtual Dom React Element React Element React Element React Element

    React Element React Element React Element React Element React Element React Element
  49. 66.

    Virtual Dom React Element React Element React Element React Element

    React Element React Element React Element React Element React Element React Element DOM Element DOM Element DOM Element DOM Element DOM Element
  50. 67.

    Virtual Dom React Element React Element React Element React Element

    React Element React Element React Element React Element React Element React Element DOM Element DOM Element DOM Element DOM Element DOM Element DOM Element DOM Element DOM Element DOM Element DOM Element
  51. 75.
  52. 76.
  53. 77.

    var PostItem = React.createClass({ render: function() { return ( <article

    className="post-item"> <VoteButton {...this.props} /> <div> <h1>{this.props.name}</h1> <p>{this.props.headline}</p> </div> <CommentButton {...this.props} /> </article> ); } });
  54. 78.

    var CommentButton = React.createClass({ propTypes: { id: React.PropTypes.number.isRequired, commentsCount: React.PropTypes.number.isRequired,

    }, getInitialState: function() { return { commentsCount: this.props.commentsCount }; }, componentDidMount: function() { Comment.observe(this.props.id, this.handleUpdate); }, componentWillUnmount: function () { Comment.stopObserving(this.props.id, this.handleUpdate); }, render: function() { return <button className="comment-button">{this.state.commentsCount}</button>; }, handleUpdate: function (count) { this.setState({ commentsCount: count }); } });
  55. 79.

    var VoteButton = React.createClass({ getInitialState: function() { return { hasVoted:

    Votes.hasVoted(this.props.id), votesCount: this.props.votesCount, }; }, componentDidMount: function() { Votes.onVoteChange(this.props.id, this.handleUpdate); }, componentWillUnmount: function () { Votes.offVoteChange(this.props.id, this.handleUpdate); }, render: function() { var className = classNames('vote-button', {active: this.state.hasVoted}); return <button className={className} onClick={this.handleClick}>{this.state.votesCount}</button> }, handleUpdate: function(count) { this.setState({ hasVoted: Votes.hasVoted(this.props.id), votesCount: count }); }, handleClick: function () { var voted = Votes.toggleVote(this.props.id); } });
  56. 82.

    // components/PostsList/index.js import Item from './Item'; export default React.createClass({ //

    ... code }); // components/PostsList/Item.js
 // ... other imports import VoteButton from './VoteButton'; import CommentButton from './CommentButton'; export default React.createClass({ // ... code }); // components/PostsList/VoteButton.js // ... other imports export default React.createClass({ // ... code }); // components/PostsList/CommentButton.js // ... other imports export default React.createClass({ // ... code });
  57. 84.
  58. 85.

    import React from 'react'; import classNames from 'classnames'; import Votes

    from '../../lib/Votes.js'; export default React.createClass({ getInitialState: function() { return this.getStateFromStore(); }, getStateFromStore: function() { return { hasVoted: VotesStore.hasVoted(this.props.id), votesCount: VotesStore.voteCount(this.props.id, this.props.votesCount) } }, componentDidMount: function() { VotesStore.addChangeListener(this.handleStoreChange); }, componentWillUnmount: function () { VotesStore.removeChangeListener(this.handleStoreChange); }, handleStoreChange: function() { this.setState(this.getStateFromStore()); }, render: function() { var className = classNames('vote-button', {active: this.state.hasVoted}); return <button className={className} onClick={this.handleClick}>{this.state.votesCount}</button> }, handleClick: function () {
  59. 86.

    import Votes from '../../lib/Votes.js'; export default React.createClass({ getInitialState: function() {

    return this.getStateFromStore(); }, getStateFromStore: function() { return { hasVoted: VotesStore.hasVoted(this.props.id), votesCount: VotesStore.voteCount(this.props.id, this.props.votesCount) } }, componentDidMount: function() { VotesStore.addChangeListener(this.handleStoreChange); }, componentWillUnmount: function () { VotesStore.removeChangeListener(this.handleStoreChange); }, handleStoreChange: function() { this.setState(this.getStateFromStore()); }, render: function() { var className = classNames('vote-button', {active: this.state.hasVoted}); return <button className={className} onClick={this.handleClick}>{this.state.votesCount}</button> }, handleClick: function () { VoteActionCreators.toggleVote(this.props.id, this.state.votesCount); } });
  60. 87.

    import React from 'react';
 import classNames from 'classnames'; import VotesStore

    from '../../stores/VotesStore'; import VoteActionCreators from '../../actions/VoteActionCreators'; export default React.createClass({ propTypes: { id: React.PropTypes.number.isRequired, votesCount: React.PropTypes.number.isRequired, }, getInitialState() { return this.getStateFromStore(); }, getStateFromStore() { return { hasVoted: VotesStore.hasVoted(this.props.id), votesCount: VotesStore.voteCount(this.props.id, this.props.votesCount) } }, componentDidMount() { VotesStore.addChangeListener(this.handleStoreChange); }, componentWillUnmount() { VotesStore.removeChangeListener(this.handleStoreChange); }, handleStoreChange() { this.setState(this.getStateFromStore()); }, render() {
  61. 88.

    votesCount: React.PropTypes.number.isRequired, }, getInitialState() { return this.getStateFromStore(); }, getStateFromStore() {

    return { hasVoted: VotesStore.hasVoted(this.props.id), votesCount: VotesStore.voteCount(this.props.id, this.props.votesCount) } }, componentDidMount() { VotesStore.addChangeListener(this.handleStoreChange); }, componentWillUnmount() { VotesStore.removeChangeListener(this.handleStoreChange); }, handleStoreChange() { this.setState(this.getStateFromStore()); }, render() { const className = classNames('vote-button', {active: this.state.hasVoted}); return <button className={className} onClick={this.handleClick}>{this.state.votesCount}</button> }, handleClick() { VoteActionCreators.toggleVote(this.props.id, this.state.votesCount); } });
  62. 89.

    // Sample Votes Store import {EventEmitter} from 'events'; import Dispatcher

    from '../dispatcher'; import {VOTE_ADDED, VOTE_REMOVED} from '../constants/VotesStoreConstants'; var state = {}; var emitter = new EventEmitter(); Dispatcher.register((payload) => { switch(payload.action) { case VOTE_ADDED: // update state emitter.emit(CHANGE_EVENT); break; case VOTE_REMOVED: // update state emitter.emit(CHANGE_EVENT); break; } } }); const CHANGE_EVENT = Symbol('CHANGE_EVENT'); default export { hasVoted(postId) { /* return state */ } voteCount(postId) { /* return state */ } addChangeListener(cb) { emitter.addListener(CHANGE_EVENT, cb); } removeChangeListener(cb) { emitter.removeListener(CHANGE_EVENT, cb); } };
  63. 90.

    // Sample Votes Store import Store from './Store'; import {VOTE_ADDED,

    VOTE_REMOVED} from '../constants/VotesStoreConstants'; var state = {}; default export Store({ hasVoted(postId) { /* return state */ } voteCount(postId) { /* return state */ } handleDispatch(payload, emit) { switch(payload.action) { case VOTE_ADDED: // update state emit(); break; case VOTE_REMOVED: // update state emit(); break; } } });
  64. 92.

    // Sample Votes Actions
 import AppDispatcher from '../dispatcher'; import {VOTE_ADDED,

    VOTE_REMOVED} from '../constants/VotesStoreConstants'; import VoteAPIUtils from '../utils/VoteAPIUtils'; import VotesStore from '../stores/VotesStore'; export default { toggleVote: function(postId, currentCount) { if (VotesStore.hasVoted(postId)) { VoteAPIUtils.remove(postId); AppDispatcher.dispatch({ action: VOTE_REMOVED, postId: postId, count: currentCount - 1 }); } else { VoteAPIUtils.remove(postId); AppDispatcher.dispatch({ action: VOTE_ADDED, postId: postId, count: currentCount + 1 }); } } };
  65. 93.
  66. 94.
  67. 95.

    • ES6 • Naming conventions • Property validation • File

    Organization • Small components • Generic components • Flux • Controlled experiments
  68. 98.
  69. 99.
  70. 101.