UPDATE – 24th September 2014:

Some critical bugs crept up in 0.1.8 which were unfortunate side-effects from the refactorings done on the project. So after spending some time busting them, we're up to 0.1.11. You can read more about them here: #80, #81, and #82. Sorry for any inconveniences.


Finally a new version of RefluxJS has been released and a bunch of stuff happened since last blog post. Since I have skipped writing a post for the 0.1.7 release so I'll include it in this blog post as well.

First things first I'd like to mention one thing that hasn't changed but is still essential:

Installation

As usual you can install the new version through node's package manager or bower. Like this:

# Install npm package
> npm install reflux
# Install bower package
> bower install reflux

If package managers aren't your thing, you can download a release from Github instead. Both zip-files and tarballs are available at your discretion.

Now that's out of the way lets look at the changes:

Changelog

Some bug fixes happened in 0.1.7 and a big refactoring for 0.1.8 and you can read about them in their respective milestone page on github.

No change is too small for me so here are some props for @warrenseine who has made a commit fixing the documentation.

You're most likely interested in new features so here is a tl;dr list:

  • Stores can push initial/default data to aggregate stores or components now using getDefaultData and a third argument in listenTo.
  • Added a bunch of convenience mixin factories for your ReactJS components, such as Reflux.listenTo and Reflux.connect
  • The preEmit hook may now transform the payload for you

Handling initial data

Thanks to the work done by @SimonDegraeve and @undoZen we have a way of handling initial data from stores. The listenTo method takes a third argument, a callback that will be called as the listener is registered.

Here is a very simple example:

var exampleStore = Reflux.createStore({  
    init: function() {},
    getDefaultData: function() {
        return "the initial data";
    }
});

// Anything that will listen to the example store
this.listenTo(exampleStore,  
    onChangeCallback,
    initialCallback); // NEW!

// initialCallback is called with whatever
// exampleStore.getDefaultData() returns

This is useful if the change event provides the same kind of argument as the getDefaultData's return value. In such instances you can reuse the callback:

this.listenTo(exampleStore,  
    onChangeCallback,
    onChangeCallback); // Keeping it DRY

Related issues and pull requests: Issue #44, PR #46, and PR #49.

More conveniences!

@krawaller has made some refactorings that lets you listen to multiple actions at once and also use a listenables property. I'll let you read the README file for this.

See PR #63.

There are also some convenience mixins for your ReactJS components. You can now use the familiar listenTo function as a mixin. Simple example follows:

var Status = React.createClass({  
    mixins: [
        Reflux.listenTo(statusStore, "onStatusChange")
    ],

    onStatusChange: function(status) {
        this.setState({
            currentStatus: status
        });
    },

    render: function() {
        // you may use `this.state.currentStatus` here
    }
});

See PR #59, PR #63.

Furthermore there is also the connect convenience mixin factory that condense the example further through above convention. This is useful if you're not doing other things with your component's state than forwarding the data given from the store's change event:

var Status = React.createClass({  
    mixins: [
        Reflux.connect(statusStore,"currentStatus")
    ],

    render: function() {
        // you may use `this.state.currentStatus` here
    }
});

See PR #75.

preEmit can now map the payload data

Depending on what you return from preEmit you may change the payload. In order to keep the same interface the default, returning nothing (technically undefined) does nothing to the payload:

action.preEmit = function() {  
   dosomething();
   // no returns
};

action(1, 2);  
// will pass on the original arguments of 1, and 2

If you return an array or an "array-like", this will be applied to the listeners:

action.preEmit = function() {  
    arguments[0] = "lawl - always lawl";
    return arguments;
};

action(1, 2);  
// will pass on "lawl - always lawl", and 2 to listeners

You can also return single values, which is useful if you want to reduce/fold the data coming in through your arguments:

action.preEmit = function() {  
    var sum = _.reduce(arguments, 
        function(m, arg) {return m+arg;}, 0);
    return sum;
};

action(1, 2);  
// will pass on 3 to listeners

See also Issue #58, PR #78

Other findings

As we refactored Reflux a little for the 0.1.8 version, we also discovered new ways to use RefluxJS.

A list for tl;dr is as follows:

  • You may now extend Reflux through the Reflux.ListenerMethods and Reflux.PublisherMethods mixins to make handling event streams from e.g. BaconJS, RxJS or Firebase even more easier to handle.
  • You can use preEmit hook for short-circuiting data flows for async

Onward to the details:

Extending RefluxJS — Oh my!

Users may now freely extend Reflux's listener and publisher methods through the Reflux.ListenerMethods and Reflux.PublisherMethods mixins that reflux uses throughout it's internals.

This is very useful if you want to create your own listenTo methods. Here is an example that listens to firebase update events:

// In your initial bootstrapping code before any stores
// are created
Reflux.ListenerMethods.listenToFirebase =  
        function(ref, callback) {
    var self = this;
    ref.on("value", function() {
        callback.apply(self, arguments);
    });
};

// Usage (in store's `init` and/or the component's `componentDidMount`):

var firebaseRef = new Firebase(url);  
this.listenToFirebase(firebaseRef, this.updateCallback);  

Want to listen to BaconJS event streams? Well, no problems! Here is sample code to get you started

// In your initial bootstrapping code before any stores
// are created
Reflux.ListenerMethods.listenToBacon =  
        function(stream, callback){
    var self = this;
    return stream.subscribe(function() {
        callback.apply(self, arguments);
    });
};

// Usage:

var baconStream = Bacon.fromArray([1, 2, 3, 4]);  
this.listenToBacon(baconStream, this.updateCallback);  

The Reactive Extensions (RxJS) is pretty much similar to BaconJS so you can use the above for RxJS observables as well.

Async? Yes, we can!

There has been a discussion about asynchronous data flows in this issue and some have come up with intriguing examples of how to handle e.g. web API's. The most interesting solution is to the preEmit hook on actions.

The idea is to split an action to several depending on an outcome of a XmlHttpRequest. Here is one simple example if you know how to use promises:

var ProductAPI = require('./ProductAPI');  
// contains `load` method that returns a promise

var ProductActions = Reflux.createActions({  
  'load',         // initiates the async load
  'loadComplete', // when the load is complete
  'loadError'     // when the load has failed
});

// Hook up the API call in the load action's preEmit
ProductActions.load.preEmit = function () {  
     // Perform the API call, get a promise
     ProductAPI.load()
          .then(ProductActions.loadComplete)
          .catch(ProductActions.loadError);

     // Pass on data from completion or from errors
     // to seperate flows using `then` or `catch` from
     // the promise
};

The hook, preEmit, provides a way for your Flux architecture to do some nifty shortcuts for some data flows.

In this case we don't need to create a store that only handles the web API stuff. This alternative, to use stores in async flows, is a bit weird since the outcome may either be successful or a failure. The stores only have one event that subsequent stores and components listen to.

Conclusion

Lots of great stuff happened for less boilerplate in your Flux architecture. Please do leave a comment below and let us know what you think.

If you find any bugs, or have any ideas, please do post that in our issues tracker on Github.

Thanks for your continued support for RefluxJS so far.