Flux Example
5-flux/app.js
It pulls in components/App.js
// **** app.js ****
var React = require('react');
var App = require('./app/components/App');
React.render(<App/>, document.body);
// **** app/components/App.js ****
var React = require('react');
var ContactsStore = require('../stores/ContactsStore');
var ViewActionCreators = require('../actions/ViewActionCreators');
var App = React.createClass({
getInitialState () {
return ContactsStore.getState();
},
// when the component first gets rendered this will get called
componentDidMount () {
// adds listener
ContactsStore.addChangeListener(this.handleStoreChange);
// then we call ActionCreators (an Action Creator, see diagram)
ViewActionCreators.loadContacts();
},
componentWillUnmount () {
ContactsStore.removeChangeListener(this.handleStoreChange);
},
handleStoreChange () {
// this gets called in the above change ChangeListener
this.setState(ContactsStore.getState());
},
renderContacts () {
return this.state.contacts.map((contact) => {
return <li>{contact.first} {contact.last}</li>;
});
},
render () {
if (!this.state.loaded) {
return <div>Loading...</div>;
}
return (
<div>
<ul>{this.renderContacts()}</ul>
</div>
);
}
});
module.exports = App;
in app/actions/ViewActionCreators we call the Dispatcher:
var { ActionTypes } = require('../Constants');
var AppDispatcher = require('../AppDispatcher');
var ApiUtil = require('../utils/ApiUtil');
var ViewActionCreators = {
loadContacts () {
// tells the _Dispatcher_
AppDispatcher.handleViewAction({
type: ActionTypes.LOAD_CONTACTS
});
// loads contacts
ApiUtil.loadContacts();
}
};
module.exports = ViewActionCreators;
ApiUtils takes care of the server communications:
var xhr = require('../lib/xhr');
var { API, ActionTypes } = require('../Constants');
var ServerActionCreators = require('../actions/ServerActionCreators');
var ApiUtils = {
loadContacts () {
xhr.getJSON(`${API}/contacts`, (err, res) => {
// callback when the response comes back
// call a new action (ServerActionCreators) with loadedContacts
ServerActionCreators.loadedContacts(res.contacts);
});
}
};
module.exports = ApiUtils;
then ServerActionCreators gets called:
var { ActionTypes } = require('../Constants');
var AppDispatcher = require('../AppDispatcher');
var ServerActionCreators = {
// again, notify the _Dispatcher_ (when contacts are loaded)
loadedContacts (contacts) {
AppDispatcher.handleServerAction({
type: ActionTypes.CONTACTS_LOADED,
contacts: contacts
});
}
};
module.exports = ServerActionCreators;
Note that the two actions will load contacts and loadContacts
are totally independent. They happen one after the other, but are not coupled. Note also that none of the elements presented so far have kept the data yet.
Move to callback Flux element. The callback is in the store:
stores/ContactSores.js
var AppDispatcher = require('../AppDispatcher');
var { EventEmitter } = require('events');
var { ActionTypes } = require('../Constants');
var assign = require('react/lib/Object.assign');
var events = new EventEmitter();
var CHANGE_EVENT = 'CHANGE';
// the internal state in the store
var state = {
contacts: [],
loaded: false
};
var setState = (newState) => {
assign(state, newState);
// emit our _Event_ (see diagram, triggers in View (components/App.js)
events.emit(CHANGE_EVENT);
};
var ContactsStore = {
addChangeListener (fn) {
events.addListener(CHANGE_EVENT, fn);
},
removeChangeListener (fn) {
events.removeListener(CHANGE_EVENT, fn);
},
getState () {
return state;
}
};
// register the _Callback_ at the _Dispatcher_
ContactsStore.dispatchToken = AppDispatcher.register((payload) => {
var { action } = payload;
console.log(action.type);
// this is where we define which action types we care about
if (action.type === ActionTypes.CONTACTS_LOADED) {
// here, `setState` is not a semantics of React, it's a custom handler
setState({
loaded: true,
contacts: action.contacts
});
}
});
module.exports = ContactsStore;
components/App.js listens to stores change event:
// **** components/App.js ****
var App = React.createClass({
getInitialState () {
return ContactsStore.getState();
},
componentDidMount () {
// listen to _Store_ changes
ContactsStore.addChangeListener(this.handleStoreChange);
ViewActionCreators.loadContacts();
},
componentWillUnmount () {
ContactsStore.removeChangeListener(this.handleStoreChange);
},
// handle the changes
handleStoreChange () {
this.setState(ContactsStore.getState());
},
..
This closes the circle. - ContactStore.dispatchToken ?In case if one store depends on another store and it has to wait for the other one to finish its calculations first.