Slide 1

Slide 1 text

React Tips & Tricks Radoslav Stankov 20/05/2015

Slide 2

Slide 2 text

Radoslav Stankov @rstankov http://rstankov.com http://github.com/rstankov

Slide 3

Slide 3 text

No content

Slide 4

Slide 4 text

No content

Slide 5

Slide 5 text

Slide 6

Slide 6 text

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

Slide 7

Slide 7 text

Hunting down more products...

Slide 8

Slide 8 text

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

Slide 9

Slide 9 text

No content

Slide 10

Slide 10 text

No content

Slide 11

Slide 11 text

No content

Slide 12

Slide 12 text

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

Slide 13

Slide 13 text

No content

Slide 14

Slide 14 text

• 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

Slide 15

Slide 15 text

No content

Slide 16

Slide 16 text

No content

Slide 17

Slide 17 text

No content

Slide 18

Slide 18 text

No content

Slide 19

Slide 19 text

No content

Slide 20

Slide 20 text

No content

Slide 21

Slide 21 text

No content

Slide 22

Slide 22 text

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

Slide 23

Slide 23 text

No content

Slide 24

Slide 24 text

No content

Slide 25

Slide 25 text

No content

Slide 26

Slide 26 text

No content

Slide 27

Slide 27 text

{post.votesCount}

{post.name}

{post.headline}

{post.commentsCount} {post.votesCount}

{post.name}

{post.headline}

{post.commentsCount}

Slide 28

Slide 28 text

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

Slide 29

Slide 29 text

Can’t tell if it is true or just trolling

Slide 30

Slide 30 text

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

Slide 31

Slide 31 text

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

Slide 32

Slide 32 text

JSX

{post.name}

{post.headline}

Slide 33

Slide 33 text

http://webpack.github.io

Slide 34

Slide 34 text

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

Slide 35

Slide 35 text

Render

Slide 36

Slide 36 text

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

Slide 37

Slide 37 text

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

Slide 38

Slide 38 text

Code conventions renderItem
 renderEmpty renderUploadProgress 
 render[Something]


Slide 39

Slide 39 text

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

Slide 40

Slide 40 text

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

Slide 41

Slide 41 text

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

Slide 42

Slide 42 text

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

Slide 43

Slide 43 text

No content

Slide 44

Slide 44 text

No content

Slide 45

Slide 45 text

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.

Slide 46

Slide 46 text

var PostList = React.createClass({ render: function() { return (
{this.renderItems()}
) }, renderItems: function () { return this.props.posts.map(function(post) { return }); } });

Slide 47

Slide 47 text

var PostItem = React.createClass({ render: function() { return ( {this.props.votesCount}

{this.props.name}

{this.props.headline}

{this.props.commentsCount} ); } });

Slide 48

Slide 48 text

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

Slide 49

Slide 49 text

No content

Slide 50

Slide 50 text

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

Slide 51

Slide 51 text

Props vs State

Slide 52

Slide 52 text

No content

Slide 53

Slide 53 text

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

Slide 54

Slide 54 text

Code conventions handlePostChange
 handleVoteToggle handleClick 
 handle[Something]


Slide 55

Slide 55 text

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

Slide 56

Slide 56 text

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

Slide 57

Slide 57 text

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

Slide 58

Slide 58 text

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

Slide 59

Slide 59 text

Virtual DOM

Slide 60

Slide 60 text

Virtual Dom React Element React Element React Element React Element React Element

Slide 61

Slide 61 text

Virtual Dom DOM Element DOM Element DOM Element DOM Element DOM Element

Slide 62

Slide 62 text

Virtual Dom React Element React Element React Element React Element React Element

Slide 63

Slide 63 text

Virtual Dom React Element React Element React Element React Element React Element

Slide 64

Slide 64 text

Virtual Dom React Element React Element React Element React Element React Element

Slide 65

Slide 65 text

Virtual Dom React Element React Element React Element React Element React Element React Element React Element React Element React Element React Element

Slide 66

Slide 66 text

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

Slide 67

Slide 67 text

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

Slide 68

Slide 68 text

Virtual Dom React Element React Element React Element React Element React Element

Slide 69

Slide 69 text

Virtual Dom div article article button div button h1 p

Slide 70

Slide 70 text

Virtual Dom PostList PostItem PostItem button div button h1 p

Slide 71

Slide 71 text

Virtual Dom PostList PostItem PostItem button div button h1 p

Slide 72

Slide 72 text

Virtual Dom PostList PostItem PostItem button div button h1 p

Slide 73

Slide 73 text

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

Slide 74

Slide 74 text

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

Slide 75

Slide 75 text

No content

Slide 76

Slide 76 text

No content

Slide 77

Slide 77 text

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

{this.props.name}

{this.props.headline}

); } });

Slide 78

Slide 78 text

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

Slide 79

Slide 79 text

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

Slide 80

Slide 80 text

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

Slide 81

Slide 81 text

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

Slide 82

Slide 82 text

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

Slide 83

Slide 83 text

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

Slide 84

Slide 84 text

FLUX

Slide 85

Slide 85 text

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

Slide 86

Slide 86 text

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

Slide 87

Slide 87 text

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

Slide 88

Slide 88 text

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

Slide 89

Slide 89 text

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

Slide 90

Slide 90 text

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

Slide 91

Slide 91 text

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

Slide 92

Slide 92 text

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

Slide 93

Slide 93 text

No content

Slide 94

Slide 94 text

Overview

Slide 95

Slide 95 text

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

Slide 96

Slide 96 text

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

Slide 97

Slide 97 text

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

Slide 98

Slide 98 text

No content

Slide 99

Slide 99 text

No content

Slide 100

Slide 100 text

@rstankov Thanks :)

Slide 101

Slide 101 text

Questions?