Share
Embed
React Tips & Tricks
Some things, I learned during an React migration, we are doing at ProductHunt.
Radoslav Stankov
May 20, 2015
Transcript
101 slides
-
app.component('Graph', function() { elements: { legend: '.legend', graph: '.graph' },
wasInitialized: function() { # ... using some js graph lib } } -
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 } }); -
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 -
• 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 -
• Close to what already had • Really easy server
side rendering • Plays great with ES6 • Virtual DOM • JSX • Data direction • Flux -
<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> -
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) ) } }); -
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> ) } }); -
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> ) } }); -
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> ) } }); -
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> ); }); } }); -
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> ); }); } }); -
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> ); }); } }); -
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 }); -
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> ); }); } }); -
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. -
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} /> }); } }); -
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> ); } }); -
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 ... }); -
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> ); } }); -
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) }); } }); -
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 }); -
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); } }); -
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({ -
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); } }); -
Virtual Dom React Element React Element React Element React Element
React Element React Element React Element React Element React Element React Element -
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 -
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 -
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> ); } }); -
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 }); } }); -
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); } }); -
// 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 }); -
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 () { -
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); } }); -
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() { -
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); } }); -
// 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); } }; -
// 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; } } }); -
// 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 }); } } }; -
• ES6 • Naming conventions • Property validation • File
Organization • Small components • Generic components • Flux • Controlled experiments