Components (React Library)

React provides a neatly organized pattern for assembling web components. Each component is comprised of a render property that supplies it’s markup. It also has a slew of available properties that hook into the components life cycle. For a simple example, if a component needs to initialize its content data it can use the “componentDidMount” property. This hook would be fired as soon as the component is finished rendering to page. More complex needs can be addressed with similiar hooks such as “componentWillMount”, “componentDidUpdate”, or “componentWillUnmount”, to name a few. A component’s structure ends up resembling this:

var MyComponent = React.createClass({

render: function (){ /* markup */ },

componentDidMount: function (){ /* Init logic */ },

componentWillUnmount: function (){ /* Teardown logic */ }

});

React of course has a few other great features up its sleeve, such as its shadow DOM, its one-way data flow pattern, and its optional JSX syntax. However, you’ll quickly be in need of something more once you reach a point where managing state becomes more involved than a simple GET request. This is where the Flux application architecture of stores, action creators and dispatchers come into play.

Stores

Flux places a component’s data source dependency on “stores”. Stores act as a data and logic hub similar to a traditional MVC model. It also provides an event emitter that can be subscribed to by components. When updates to data are performed, the store will emit the change to its listening components.

var activeChannel = null; /* Our data */ var ChannelStore = assign({}, EventEmitter.prototype, {



/* Emitter Functions */ addChangeListener: function (callback) {

this.on(EVENT_EMITTER, callback);

},



removeChangeListener: function (callback) {

this.removeListener(EVENT_EMITTER, callback);

},



emitChange: function () {

this.emit(EVENT_EMITTER);

}, /* Data API */ getActiveChannel: function () {

return activeChannel;

}

}); /* In this example, the object-assign library is used to merge the store with Node's EventEmitter module, giving it the emit & listener methods used. */

One of the key concepts here is that a component never updates a store. Instead, it creates an action that enters a uniquely Flux life cycle.

Actions and Action Creators

Action creators (which appears to be a working title) are plain javascript objects that provide a factory for actions.

var ChannelActionCreator = {

ChangeChannel: function (newChannel) {

Dispatcher.dispatch({

actionType: "ChangeChannel",

channelName: newChannel

});

}

};

/* In use */

ChannelActionCreator.ChangeChannel("Random");

When a component needs to create a particular action it calls the corresponding method on an action creator, passing it any relevant data for that action. The action function packs an object with the action type and data. It then hands it off to the application’s dispatcher. At this point the action creator’s job is done and the dispatcher takes over the cycle.

The Dispatcher

The dispatcher is actually quite simple and very similar to a pub/sub service. There are two key differences:

The dispatcher notifies all stores of the action. The stores can choose to queue themselves behind other stores, meaning one store can wait for another store to finish its callback prior to firing its own callback.

There are many benefits to these paradigms. Lets look at an example use case to vet them out. Picture a chat app (Slack) that has multiple chat channels. One store manages the channels and another store manages the messages being displayed. Both stores need to update their data when a user changes channels. Using the queueing system, the message component can wait for the channel to perform its changes before it updates. This helps prevent any chance of the selected channel falling out of sync with the displayed messages.

Best of all, the dispatcher code works right out of the box without any changes or configuration.

Back to Stores

The second section of a store is its register with the Dispatcher. Notice the caching of the dispatchToken. It’ll come into play in just a second.

ChannelStore.dispatchToken = Dispatcher.register(function(action) {

switch(action.actionType) {

case “ChangeChannel”:

activeChannel = action.channelName;

ChannelStore.emitChange();

break;

}

});

The message store is dependent on the channel store for supplying data on what channel is active. Because of this, if a change_channel action is created, it is imperative that the channel store updates prior to the message store. The message store does this by adding a Dispatcher.waitFor() call, referencing the ChannelStore’s token, prior to its digest of the action. The dispatcher would in return queue the channel store’s callback before the message store, ensuring a correct update order.

MessageStore.dispatchToken = Dispatcher.register(function(action) { switch(action.actionType) {

case “ChangeChannel”:

Dispatcher.waitFor([

ChannelStore.dispatchToken

]};

MessageStore.updateMessages();

break;

}

});

The beauty of this pattern is the channel store has no idea it has a dependent store. In theory, all stores can act completely agnostic of any dependent stores. Also, by looking at this registry within the messageStore’s code, I can clearly see that it is dependent on the channelStore. In other arcitectures, that sort of dependency can often be buried in some sort of class method, making it difficult to identify easily.