Flux
Flux is an architecture that Facebook uses internally when working with React . It is not a framework or a library. It is simply a new kind of architecture that complements React and the concept of Unidirectional Data Flow .
That said, Facebook does provide a repo that includes a Dispatcher library. The dispatcher is a sort of global pub/sub handler that broadcasts payloads to registered callbacks.
A typical Flux architecture will leverage this Dispatcher library, along with NodeJS's EventEmitter module in order to set up an event system that helps manage an applications state.
Components
- Actions β Helper methods that facilitate passing data to the Dispatcher
- Dispatcher β Receives actions and broadcasts payloads to registered callbacks
- Stores β Containers for application state & logic that have callbacks registered to the dispatcher
- Controller Views β React Components that grab the state from Stores and pass it down via props to child components
Flux helps to solve some of the difficulty we run into with unidirectional data flow when it comes to changing Application State that is higher up the virtual DOM than the Components that alter that State themselves.
Controllers do exist in a Flux application, but they are controller-views.
Action creators β dispatcher helper methods β are used to support a semantic API that describes all changes that are possible in the application. It can be useful to think of them as a fourth part of the Flux update cycle
How does the API relate to this?
When you are working with data that is coming from (or going to) the outside, Iβve found that using Actions to introduce the data into the Flux Flow, and subsequently Stores, is the most painless way to go about it.
Action Creators & Actions
Action Creators are collections of methods that are called within views (or anywhere else for that matter) to send actions to the Dispatcher. Actions are the actual payloads that are delivered via the dispatcher.
The way Facebook uses them, action type constants are used to define what action should take place, and are sent along with action data. Inside of registered callbacks, these actions can now be handled according to their action type, and methods can be called with action data as the arguments.
// ES5
var keyMirror = require('react/lib/keyMirror');
module.exports = keyMirror({
LOAD_SHOES: null
});
// ES6
import keyMirror from 'react';
export keyMirror({
LOAD_SHOES: null
});
Above we use Reactβs keyMirror library to mirror our keys so that our value matches our key definition. Just by looking at this file, we can tell that our app loads shoes. The use of constants helps keep things organized, and helps give a high level view of what the app actually does. Now lets take a look at the corresponding Action Creator definition:
// ES 5
var AppDispatcher = require('../dispatcher/AppDispatcher');
var ShoeStoreConstants = require('../constants/ShoeStoreConstants');
var ShoeStoreActions = {
loadShoes: function(data) {
AppDispatcher.handleAction({
actionType: ShoeStoreConstants.LOAD_SHOES,
data: data
})
}
};
module.exports = ShoeStoreActions;
// ES6
import AppDispatcher from '../dispatcher/AppDispatcher';
import ShoeStoreConstants from '../constants/ShoeStoreConstants';
class ShoeStoreActions {
public function loadShoes(data) {
AppDispatcher.handleAction({
actionType: ShoeStoreConstants.LOAD_SHOES,
data: data
});
return;
}
}
In our example above, we created a method on our ShoeStoreActions object that calls our dispatcher with the data we provided. We can now import this actions file into our view or API, and call ShoeStoreActions.loadShoes(ourData) to send our payload to the Dispatcher, which will broadcast it. Then the ShoeStore will βhearβ that event and call a method thats loads up some shoes!
/** @jsx React.DOM */
var React = require('react');
var ShoesStore = require('../stores/ShoeStore');
// Method to retrieve application state from store
function getAppState() {
return {
shoes: ShoeStore.getShoes()
};
}
// Create our component class
var ShoeStoreApp = React.createClass({
// Use getAppState method to set initial state
getInitialState: function() {
return getAppState();
},
// Listen for changes
componentDidMount: function() {
ShoeStore.addChangeListener(this._onChange);
},
// Unbind change listener
componentWillUnmount: function() {
ShoesStore.removeChangeListener(this._onChange);
},
render: function() {
return <ShoeStore shoes={this.state.shoes} />;
},
// Update view state when change event is received
_onChange: function() {
this.setState(getAppState());
}
});
module.exports = ShoeStoreApp;
In the example above, we listen for change events using addChangeListener, and update our application state when the event is received. Our application state data is held in our Stores, so we use the public methods on the Stores to retrieve that data and then set our application state.