$30 off During Our Annual Pro Sale. View Details »

React Tips & Tricks

React Tips & Tricks

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

Radoslav Stankov

May 20, 2015
Tweet

More Decks by Radoslav Stankov

Other Decks in Technology

Transcript

  1. React Tips & Tricks
    Radoslav Stankov 20/05/2015

    View Slide

  2. Radoslav Stankov
    @rstankov

    http://rstankov.com

    http://github.com/rstankov

    View Slide

  3. View Slide

  4. View Slide





  5. View Slide

  6. app.component('Graph', function() {
    elements: {
    legend: '.legend',
    graph: '.graph'
    },
    wasInitialized: function() {
    # ... using some js graph lib
    }
    }

    View Slide



  7. Hunting down more products...

    View Slide

  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
    }
    });

    View Slide

  9. View Slide

  10. View Slide

  11. View Slide

  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

    View Slide

  13. View Slide

  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

    View Slide

  15. View Slide

  16. View Slide

  17. View Slide

  18. View Slide

  19. View Slide

  20. View Slide

  21. View Slide

  22. • Close to what already had
    • Really easy server side rendering
    • Plays great with ES6
    • Virtual DOM
    • JSX
    • Data direction
    • Flux

    View Slide

  23. View Slide

  24. View Slide

  25. View Slide

  26. View Slide



  27. {post.votesCount}

    {post.name}
    {post.headline}

    {post.commentsCount}


    {post.votesCount}

    {post.name}
    {post.headline}

    {post.commentsCount}



    View Slide

  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)
    )
    }
    });

    View Slide

  29. Can’t tell if it is true
    or just trolling

    View Slide

  30. var PostList = React.createClass({
    render: function() {
    var posts = this.props.posts.map(function(post) {
    return (

    {post.votesCount}

    {post.name}
    {post.headline}

    {post.commentsCount}

    );
    });
    return (

    {posts}

    )
    }
    });

    View Slide

  31. STILL can’t tell if it is true
    or just trolling

    View Slide

  32. JSX



    {post.name}
    {post.headline}



    View Slide

  33. http://webpack.github.io

    View Slide

  34. var PostList = React.createClass({
    render: function() {
    var posts = this.props.posts.map(function(post) {
    return (

    {post.votesCount}

    {post.name}
    {post.headline}

    {post.commentsCount}

    );
    });
    return (

    {posts}

    )
    }
    });

    View Slide

  35. Render

    View Slide

  36. var PostList = React.createClass({
    render: function() {
    var posts = this.props.posts.map(function(post) {
    return (

    {post.votesCount}

    {post.name}
    {post.headline}

    {post.commentsCount}

    );
    });
    return (

    {posts}

    )
    }
    });

    View Slide

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

    {this.renderItems()}

    )
    },
    renderItems: function () {
    return this.props.posts.map(function(post) {
    return (

    {post.votesCount}

    {post.name}
    {post.headline}

    {post.commentsCount}

    );
    });
    }
    });

    View Slide

  38. Code conventions
    renderItem

    renderEmpty
    renderUploadProgress

    render[Something]


    View Slide

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

    {this.renderItems()}

    )
    },
    renderItems: function () {
    return this.props.posts.map(function(post) {
    return (

    {post.votesCount}

    {post.name}
    {post.headline}

    {post.commentsCount}

    );
    });
    }
    });

    View Slide

  40. var PostList = React.createClass({
    propTypes: {
    posts: React.PropTypes.array.isRequired
    },
    getDefaultProps: function() {
    return { posts: [] };
    },
    render: function() {
    return (

    {this.renderItems()}

    )
    },
    renderItems: function () {
    return this.props.posts.map(function(post) {
    return (

    {post.votesCount}

    {post.name}
    {post.headline}

    {post.commentsCount}

    );
    });
    }
    });

    View Slide

  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
    });

    View Slide

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

    {this.renderItems()}

    )
    },
    renderItems: function () {
    return this.props.posts.map(function(post) {
    return (

    {post.votesCount}

    {post.name}
    {post.headline}

    {post.commentsCount}

    );
    });
    }
    });

    View Slide

  43. View Slide

  44. View Slide

  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.

    View Slide

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

    {this.renderItems()}

    )
    },
    renderItems: function () {
    return this.props.posts.map(function(post) {
    return
    });
    }
    });

    View Slide

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

    {this.props.votesCount}

    {this.props.name}
    {this.props.headline}

    {this.props.commentsCount}

    );
    }
    });

    View Slide

  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 ...
    });

    View Slide

  49. View Slide

  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 (

    {this.props.votesCount}

    {this.props.name}
    {this.props.headline}

    {this.props.commentsCount}

    );
    }
    });

    View Slide

  51. Props vs State

    View Slide

  52. View Slide

  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 (

    {this.state.votesCount}

    {this.props.name}
    {this.props.headline}

    {this.props.commentsCount}

    );
    },
    handleVoteClick: function () {
    var voted = Votes.toggleVote(this.props.id);
    this.setState({
    hasVoted: voted,
    votesCount: this.state.votesCount + (voted ? 1 : -1)
    });
    }
    });

    View Slide

  54. Code conventions
    handlePostChange

    handleVoteToggle
    handleClick

    handle[Something]


    View Slide

  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 (

    {this.state.votesCount}

    {this.props.name}
    {this.props.headline}

    {this.state.commentsCount}

    );
    },
    handleVotesUpdate: function(count) {
    this.setState({
    hasVoted: Votes.hasVoted(this.props.id),
    votesCount: count
    });

    View Slide

  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 (

    {this.state.votesCount}

    {this.props.name}
    {this.props.headline}

    {this.state.commentsCount}

    );
    },
    handleVotesUpdate: function(count) {
    this.setState({
    hasVoted: Votes.hasVoted(this.props.id),
    votesCount: count
    });
    },
    handleVoteClick: function () {
    Votes.toggleVote(this.props.id);
    }
    });

    View Slide

  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 (

    {this.state.votesCount}

    {this.props.name}
    {this.props.headline}

    {this.state.commentsCount}

    );
    },
    handleCommentsUpdate: function (count) {
    this.setState({

    View Slide

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

    {this.state.votesCount}

    {this.props.name}
    {this.props.headline}

    {this.state.commentsCount}

    );
    },
    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);
    }
    });

    View Slide

  59. Virtual DOM

    View Slide

  60. Virtual Dom
    React
    Element
    React
    Element
    React
    Element
    React
    Element
    React
    Element

    View Slide

  61. Virtual Dom
    DOM
    Element
    DOM
    Element
    DOM
    Element
    DOM
    Element
    DOM
    Element

    View Slide

  62. Virtual Dom
    React
    Element
    React
    Element
    React
    Element
    React
    Element
    React
    Element

    View Slide

  63. Virtual Dom
    React
    Element
    React
    Element
    React
    Element
    React
    Element
    React
    Element

    View Slide

  64. Virtual Dom
    React
    Element
    React
    Element
    React
    Element
    React
    Element
    React
    Element

    View Slide

  65. Virtual Dom
    React
    Element
    React
    Element
    React
    Element
    React
    Element
    React
    Element
    React
    Element
    React
    Element
    React
    Element
    React
    Element
    React
    Element

    View Slide

  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

    View Slide

  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

    View Slide

  68. Virtual Dom
    React
    Element
    React
    Element
    React
    Element
    React
    Element
    React
    Element

    View Slide

  69. Virtual Dom
    div
    article article
    button div button
    h1 p

    View Slide

  70. Virtual Dom
    PostList
    PostItem PostItem
    button div button
    h1 p

    View Slide

  71. Virtual Dom
    PostList
    PostItem PostItem
    button div button
    h1 p

    View Slide

  72. Virtual Dom
    PostList
    PostItem PostItem
    button div button
    h1 p

    View Slide

  73. Virtual Dom
    PostList
    PostItem PostItem
    Vote
    Button
    div
    Comment
    Button
    h1 p

    View Slide

  74. Virtual Dom
    PostList
    PostItem PostItem
    Vote
    Button
    div
    Comment
    Button
    h1 p

    View Slide

  75. View Slide

  76. View Slide

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



    {this.props.name}
    {this.props.headline}



    );
    }
    });

    View Slide

  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 {this.state.commentsCount};
    },
    handleUpdate: function (count) {
    this.setState({
    commentsCount: count
    });
    }
    });

    View Slide

  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 {this.state.votesCount}
    },
    handleUpdate: function(count) {
    this.setState({
    hasVoted: Votes.hasVoted(this.props.id),
    votesCount: count
    });
    },
    handleClick: function () {
    var voted = Votes.toggleVote(this.props.id);
    }
    });

    View Slide

  80. components/PostList.js
    components/PostItem.js
    components/VoteButton.js
    components/CommentButton.js

    View Slide

  81. components/PostList/index.js (List.js)
    components/PostList/Item.js
    components/PostList/VoteButton.js
    components/PostList/CommentButton.js

    View Slide

  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
    });

    View Slide

  83. // components/index.js
    import PostList from 'PostList/';

    View Slide

  84. FLUX

    View Slide

  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 {this.state.votesCount}
    },
    handleClick: function () {

    View Slide

  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 {this.state.votesCount}
    },
    handleClick: function () {
    VoteActionCreators.toggleVote(this.props.id, this.state.votesCount);
    }
    });

    View Slide

  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() {

    View Slide

  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 {this.state.votesCount}
    },
    handleClick() {
    VoteActionCreators.toggleVote(this.props.id, this.state.votesCount);
    }
    });

    View Slide

  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); }
    };

    View Slide

  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;
    }
    }
    });

    View Slide

  91. // constants/VotesStoreConstants.js
    export const VOTE_ADDED = Symbol('VOTE_ADDED');
    export const VOTE_REMOVED = Symbol('VOTE_REMOVED');

    View Slide

  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
    });
    }
    }
    };

    View Slide

  93. View Slide

  94. Overview

    View Slide

  95. • ES6
    • Naming conventions
    • Property validation
    • File Organization
    • Small components
    • Generic components
    • Flux
    • Controlled experiments

    View Slide

  96. https://speakerdeck.com/rstankov/react-tips-and-tricks

    View Slide

  97. https://github.com/rstankov/talks-code

    View Slide

  98. View Slide

  99. View Slide

  100. @rstankov
    Thanks :)

    View Slide

  101. Questions?

    View Slide