It's more like directives part of Angular (don't say it out loud)
React has no…
… controllers
… directives
… templates
… global event listeners
… models
… no view models
//HTML
<header>
<div class="name"></div>
</header>
//JAVASCRIPT
$.post('/login', credentials, function( user ) {
$('header .name').show().text( user.name );
});
//JAVASCRIPT
class Header extends React.Component{
constructor(){
this.state = {name: null};
}
componentDidMount(){
$.post('/login', credentials, function( user ) {
this.setState({name: user.name});
});
}
render() {
return <header>
{ this.state.name ? <div>this.state.name</div> : null }
</header>;
}
}
React.render(<Header />, document.body);
//JAVASCRIPT
var MyComponent = React.createClass({
propTypes: { name: React.PropTypes.string.isRequired },
mixins : [],
getInitialState: function() { return {count: 1} },
getDefaultProps: function() { return {name: "Albert Pilon"} },
componentWillMount : function() {
setInterval(function(){
this.setState({count : this.state.count + 1})
}, 1000)
},
componentWillReceiveProps: function() {},
componentWillUnmount : function() {},
_trigram : function() {
return trucpourfaireuntrigram(this.props.name);
},
_secondFunction : function() {},
render: function() {
return <h1>
{this.props.name} ({this._trigram}) a {this.state.count} bonbon{this.state.count > 1 && "s"}
</h1>;
}
})
React.render(<MyComponent />, document.body);
React.render(<MyComponent name="Stéphane Montlouis-Calixte" />, document.body);
var CommentBox = React.createClass({
loadCommentsFromServer: function() {
$.ajax({
url: this.props.url,
success: function(data) {
this.setState({data: data});
}.bind(this),
/* .. */
});
},
getInitialState: function() {
return {data: []};
},
componentDidMount: function() {
this.loadCommentsFromServer();
setInterval(this.loadCommentsFromServer, this.props.pollInterval);
},
render: function() {
return (
<div className="commentBox">
<h1>Comments</h1>
<CommentList data={this.state.data} />
<CommentForm />
</div>
);
}
});
React.render(
<CommentBox url="comments.json" pollInterval={2000} />,
document.getElementById('content')
);
var CommentBox = /* ... */
var CommentList = React.createClass({
render: function() {
var commentNodes = this.props.data.map(function (comment) {
return (
<Comment author={comment.author}>
{comment.text}
</Comment>
);
});
return (
<div className="commentList">
{commentNodes}
</div>
);
}
});
var Comment = React.createClass({
render: function() {
var rawMarkup = marked(this.props.children.toString(), {sanitize: true});
return (
<div className="comment">
<h2 className="commentAuthor">
{this.props.author}
</h2>
<span dangerouslySetInnerHTML={{__html: rawMarkup}} />
</div>
);
}
});
React.render(
<CommentBox />,
document.getElementById('content')
);
Stateful -> Stateless components | Avoid state
Centralize state
State should not be set from props
Always set propTypes for validation and self-documentation
Multi-line JSX
Conditional JSX
Use the purerender mixin
var ReactComponentWithPureRenderMixin = {
shouldComponentUpdate: function(nextProps, nextState) {
return !shallowEqual(this.props, nextProps) ||
!shallowEqual(this.state, nextState);
}
};
This only shallowly compares the objects. If these contain complex data structures, it may produce false-negatives for deeper differences. Only mix into components which have simple props and state, or use forceUpdate() when you know deep data structures have changed.
var a = { foo: 'bar' };
var b = a;
a === b; // true
var a = { foo: 'bar' };
var b = { foo: 'bar' };
a === b; // false
var a = { foo: 'bar' };
var b = a;
b.foo = 'baz';
a === b; // true
//WTF ?! >:(
Immutable to the rescue !
When the past is not preserved, it will come back to haunt you, manifesting as bugs in the program.
Primitives
*wrapper type
//IMMUTABLE
var me = new String("Angus");
me.length = 2; //(error in strict mode)
me.length; //5 (not 2)
me.valueOf(); "Angus"
var s = "hello"; // Start with some lowercase text
s.toUpperCase(); // Returns "HELLO", but doesn't alter s
s // => "hello": the original string has not changed
//MUTABLE
var o = { y:1 }; // Start with an object
o.y = 2; // Mutate it by changing the value of a property ...
x=[1,2,3];
x.reverse();
alert(x[0]); // 3
var obj = {
prop: function (){},
toto: "truc"
}
obj.toto = "machin";
obj.bidule = "woof";
var immObj = Object.freeze(obj);
// Maintenant que l'objet est gelé, les changements échoueront
obj.toto = "eheh"; // échoue silencieusement
obj.roxor = "ga bu zo meu"; // échoue silencieusement et n'ajoute pas la propriété
Fake Immutability with Object.Freeze()
var obj1 = Immutable.Map({
foo: "salut",
bar: "bonjour",
baz: "zeze"
});
var obj2 = map1.set('foo', "bonjour");
obj1.get('foo'); // salut
obj2.get('foo'); // bonjour
//Array methods return a new immutable Object
var list1 = Immutable.List.of(1, 2);
var list2 = list1.push(3, 4, 5);
var list3 = list2.unshift(0);
var list4 = list1.concat(list2, list3);
For application that deals with dynamic data
"MVC Does Not Scale, Use Flux Instead" - Abraham Lincoln
var EventDispatcher = {
_prefix: 'on_',
listeners: {},
register: function(evt_name, bind, callback) {
var _evt_name = this._prefix + evt_name;
if(typeof this.listeners[_evt_name] == 'undefined') {
this.listeners[_evt_name] = [];
}
this.listeners[_evt_name].push([bind === null ? this : bind, callback]);
},
emit: function(evt_name, params) {
var _evt_name = this._prefix + evt_name;
if(typeof this.listeners[_evt_name] != 'undefined') {
for(var i = 0, l = this.listeners[_evt_name].length; i < l; i++) {
this.listeners[_evt_name][i][1].call(this.listeners[_evt_name][i][0], evt_name, params);
}
}
}
}
EventDispatcher.register('myevent', object1.dosomething);
// inside an object2 method
EventDispatcher.emit('myevent', { custom: 'parameter' });
//Dispatcher.js
var AppDispatcher = new Dispatcher();
//Button.react.js
<button onClick={ this.createNewItem }>New Item</button>
createNewItem: function( evt ) {
ListActions.add( item );
}
//ListActions.js
ListActions = {
add: function( item ) {
AppDispatcher.dispatch({
eventName: 'new-item',
newItem: item
});
}
};
// Global object representing list data and logic
var ListStore = {
// Actual collection of model data
items: [],
// Accessor method we'll use later
getAll: function() {
return this.items;
}
switch( payload.eventName ) {
case 'new-item':
// We get to mutate data!
ListStore.items.push( payload.newItem );
// Tell the world we changed!
ListStore.trigger( 'change' );
break;
}
return true; // Needed for Flux promise resolution
};
//React component
componentDidMount: function() {
ListStore.bind( 'change', this.listChanged );
},
listChanged: function() {
// Since the list changed, trigger a new render.
this.setState({
list: ListStore.getAll()
});
},
setState executes render() method, and you're done
Single, Normalized Application State
//Whole motherfucking app
myFuckingApp = {
app: {
menu_open: true
},
auth: {
data: null,
form: null
},
i18n: {
formats: {},
locales: initialLocale,
messages: messages[initialLocale]
},
pendingActions: {},
todos: {
editables: {},
newTodo: {
title: ''
},
// Initial state can contain prefetched lists and maps. List for array, map
// for object. We can also use sortedByTitle list, if we need sorted data.
list: [
{id: 1, title: 'consider ‘stop doing’ app'},
{id: 1, title: 'consider ‘stop doing’ app'},
{id: 1, title: 'consider ‘stop doing’ app'},
{id: 1, title: 'consider ‘stop doing’ app'},
{id: 1, title: 'consider ‘stop doing’ app'}
]
},
user: {
isLoggedIn: false,
data: null
}
};
<Router state="myFuckingApp">
<App app="this.props.state">
<Container container="this.props.app">
<Header header="this.props.container.header">
<Menu menu="this.props.header.menu">
{this.props.menu.is_open ? <Menu_open> : <Menu_closed> }
var App = React.createClass({
getInitialState: function () {
return {
"a": 10,
"b": {
"foo": {
"bar": 42,
"baz": ['red', 'green']
}
}
};
},
render: function () {
return <pre>{JSON.stringify(this.state, undefined, 2)}</pre>;
}
});
var Cursor = require('path/to/react-cursor').Cursor;
var cursor = Cursor.build(this)
cursor.refine('a').value //=> 10
cursor.refine('a').set(11); //=> a set to 11
cursor.refine('b').refine('foo').value //=> { 'bar': 42, 'baz': ['red', 'green'] }
cursor.refine('b').refine('foo').set({ 'bar': 43, 'baz': ['red', 'green'] })
cursor.refine('b', 'foo', 'baz', 1).set('blue')