{
"type": "NEW_MESSAGE",
"data": {
"id": 42,
"message": "Hello Meta Refresh"
}
}
Central broker that sends actions to registered stores
We went with Facebook's own implementation
Callbacks are not subscribed to particular events
Every payload is dispatched to every registered callback
Callbacks can be deferred
...
Dispatcher.register(function(payload) {
if (payload.type === 'NEW_MESSAGE') {
console.log(payload.message);
}
});
...
...
// On some events: Web Sockets, API Responses, etc.
Dispatcher.dispatch({
type: 'NEW_MESSAGE',
message: 'Hello Meta Refresh'
});
...
class Store {
constructor(spec, dispatchToken) {
this.dispatchToken = dispatchToken;
assign(this, EventEmitter.prototype, spec);
this.init();
}
emitChange() {
this.emit('change');
}
addChangeListener(callback) {
this.on('change', callback);
}
removeChangeListener(callback) {
this.removeListener('change', callback);
}
}
var UserStore = new Store({
getAll: function() {
if (Object.keys(this._items).length === 0) {
this._items = {
state: 'PENDING'
};
UserActions.get();
}
return this._items;
},
getByID: function(id) {
if (!this._items[id]) {
this._items[id] = {
state: 'PENDING'
};
UserActions.get(id);
}
return this._items[id];
}
}, function () { ... });
var UserStore = new Store({
...
}, Dispatcher.register(payload) {
var type = payload.type,
users = payload.users;
switch(type) {
case 'USER_GET_SUCCESS':
users.forEach(function(user) {
this._items[user.id] = user;
}.bind(this));
this.emitChange();
break;
case 'USER_GET_FAILURE':
this._items = {
state: 'FAILURE'
};
this.emitChange();
break;
};
});
UserActions = {
get: function(id) {
Dispatcher.dispatch({
type: 'USER_GET_PENDING'
});
// API Wrapper
getUsers(id, function(err, users) {
if (err) {
Dispatcher.dispatch({
type: 'USER_GET_FAILURE',
error: err
});
} else {
Dispatcher.dispatch({
type: 'USER_GET_SUCCESS',
users: users
});
}
});
}
};
Not just models and collections
Listens and act on actions
Stores don't mutate themselves
Singletons
May talk to other stores, but we avoided it
var UserList = React.createClass({
componentWillMount: function() {},
componentDidUpdate: function() {},
getInitialState: function() {},
getStateFromStores: function() {},
setStateFromStores: function() {},
render: function() {}
});
...
getInitialState: function() {
return {
users: this.getStateFromStores()
};
},
getStateFromStores: function() {
return UserListStore.getAll();
},
setStateFromStores: function() {
this.setState({
users: this.getStateFromStores()
});
}
...
...
componentDidMount: function() {
UserIndexStore.addChangeListener(
this.setStateFromStores);
},
componentWillUnmount: function() {
UserIndexStore.removeChangeListener(
this.setStateFromStores);
}
...
...
render: function() {
var users = this.state.users;
return (
<table>
{users.map(function(user) {
return (
<tr>
<td>{user.name}</td>
<td>{user.email}</td>
</tr>
);
})}
</table>
);
}
...
!
.
var UserList = React.createClass({
...
onSubmit: function(e) {
e.preventDefault();
if (this.state.isNew) {
UserActions.create(
this.state.user.toJSON());
} else {
UserActions.update(
this.state.user.toJSON());
}
}
...
});
UserActions = {
...
create: function(user) {
Dispatcher.dispatch({
type: 'USER_CREATE',
message: 'Creating User "' + user.username + '"',
item: user
});
// The async bit
createUser(user).then(function(data) {
Dispatcher.dispatch({
type: 'USER_CREATE_SUCCESS',
item: user,
data: data
});
}, function(err) {
Dispatcher.dispatch({
type: 'USER_CREATE_FAILURE',
item: user,
error: err
});
});
}
}
Data dependency between stores
IndexStore `waitFor` ItemStore
class IndexStore extends Store {
constructor(ItemStore, spec, dispatcherCallback) {
super(spec, Dispatcher.register(function(payload) {
if (payload.action.type === this.GET_SUCCESS_ACTION) {
// Wait for the related item stores to finish
Dispatcher.waitFor([ItemStore.dispatchToken]);
...
describe('User list testing', () => {
it('Renders a loading page', () => {
// Render the Expiry tab into the document
var userlist = TestUtils.renderIntoDocument('<UserList/>'),
node = expiry.getDOMNode();
expect(node.textContent).to.be("LOADING");
});
it('Shows atlest one user', () => {
// Fire user get action
Dispatcher.fire({
type: 'USER_GET_SUCCESS',
data: [{
name: 'John Doe'
}]
});
expect(node.textContent).to.be(...);
}):
});