Not using MVC with ReactJS

I'm going to do some examples of the single directional data-flow architecture by comparing ReactJS reference implementation of Flux and using a library I wrote, called Reflux.

The developers at Facebook behind ReactJS have some disdain of MVC frameworks by escewing them. Having used MVC pattern in a professional capacity and this library for a while I can see what the hub-bub is about. Sooner or later, you'll run into issues with how you should handle data and ReactJS doesn't really care that much on how it's data gets passed in or how that data should be handled across the web application. This is mostly an architectural problem that is outside the scope of ReactJS itself. The fine developers over at Facebook did however advice a functional approach they call Flux.

Basically the idea behind Flux is to have a much more functional approach to how data is handled in a web application. Flux introduces the concepts of Actions and Data Stores for handling events and data across the application. The data-flow can be explained in a simple piece of ASCII art:

Action -> Data Store -> Component

Mutation of data has to happen through calling the Actions. The Data Stores themselves have to listen on the actions and mutate the data within the store. This keeps the data structure flat and let the concerns of data manipulation be present in the Stores. In turn this is to remove side effects that may happen from Components handling data on their own.

By forcing the data-flow to be in a single direction, it will be easier to follow how data changes will affect the whole application depending on what actions have been issued. Components themselves may only mutate application-wide data by executing an action to avoid double maintenance nightmares.

The Todo Example vs Reflux

There is a reference example available, of a Todo list. I'm going to base my deconstruction on this example.

Facebook likes to tout Flux as a functional programming way of creating applications, however I found some tendencies that are a blast to the imperative programming past and can be improved with much simpler and a lot more functional implementation.

The dispatcher weirdness

The TODO implementation involves a Dispatcher that marshals the actions. Then the Data Stores need to do the following:

Register itself to listen to action events. All of the actions.

To differentiate between the actions, the Stores need to compare "static" strings with the action they want to listen to

The last point made me a bit puzzled because this kind breaks the beauty of functional programming concepts that JavaScript is capable of doing. I have a big disdain against comparing types, either through Strings or through the use of instanceOf . This throws the beauty of polymorphism out the window and opens up a can of worms to maintain.

In Reflux I decided to move the dispatcher into the actions themselves and remove the singleton implementation. So when you're using actions, your application only needs to do two things:

Create actions Listen to action invocation via callbacks

Actions are created with Reflux.createAction and can be listened to by any Data Store and component by passing a callback to the action's listen method, like this:

// Creating action var toggleGem = Reflux.createAction(); // Listening to action var isGemActivated = false; toggleGem.listen(function() { isGemActivated = !isGemActivated; var strActivated = isGemActivated ? 'activated' : 'deactivated'; console.log('Gem is ' + strActivated); });

Actions in Reflux are basically functors with event emitters. If you'd like to see a simple implementation of them check out the Reflux Gist I made before I started on the project.

All you need to do to start mutating the data in your application is to call the action itself:

toggleGem(); // The callback that listens to the action will output // "Gem is activated" toggleGem(); // Will output "Gem is deactivated"

By making actions as their own functors this way, instead of having a file of static strings, you now have a file of actions to choose from:

var Actions = {}; Actions.toggleGem = createAction(); Actions.polishGem = createAction(); // and so on...

The data stores are also simple to implement as well and Reflux provides a convenience createStore function to do this for you. Developers who have dabbled in creating components in ReactJS will recognize this function as it works a lot like React.createClass :

// Creates a DataStore var gemStore = Reflux.createStore({ // Initial setup init: function() { this.isGemActivated = false; // Register statusUpdate action this.listenTo(toggleGem, this.handleToggleGem); }, // Callback handleToggleGem: function() { this.isGemActivated = !this.isGemActivated; // Pass on to listeners through // the DataStore.trigger function this.trigger(this.isGemActivated); } });

Note that there are a couple of convenience functions on the store. E.g. listenTo , that takes care of action registering, and trigger that triggers the change event in the DataStore.

ReactJS components can use these data stores by listening to them:

var Gem = React.createClass({ componentDidMount: function() { // the listen function returns a // unsubscription convenience functor this.unsubscribe = gemStore.listen(this.onGemChange); }, componentWillUnmount: function() { this.unsubscribe(); }, // The listening callback onGemChange: function(gemStatus) { this.setState({gemStatus: gemStatus}); }, render: function() { var gemStatusStr = this.state.gemStatus ? "activated" : "deactivated"; return (React.DOM.h1(null, 'Gem is ' + gemStatusStr)); } });

What is waitFor really for?

The other thing that was puzzling me with the code of the TodoList example was that they included a waitFor functionality that is also admittedly broken. The situation is that a data store needs to wait for other datastores to complete their data handling after a particular action has been executed. This seems to go away from the single direction data-flow principle.

Wouldn't it be easier if the Data Stores were listenable as well? Well, the way they're implemented in Reflux, you can aggregate the DataStores by listening to another data store's change event.

In conclusion

Please do try out Reflux in your ReactJS project for a much simpler setup for the Flux architecture. Let me know of any other features you'd like to see implemented or if you've found any bugs.

It is available both as a bower component ( bower install reflux ) and as an npm package ( npm install reflux ) if you'd like to use it server-side.

Have fun Refluxing!