Keeping Flux Flexible with general-store

Last fall at HubSpot, we started to experiment with React and Flux as the foundation for the future of our web products. Having experienced the challenges that accompany large Backbone applications, we’re confident that React’s declarative, composable components and the Flux uni-directional data model will alleviate some of the growing pains we’ve run into with Backbone.

Over the last few months, there’s been a lot of buzz about Flux. Flux is mostly an architectural concept, but there are quite a few frameworks out there (e.g. Fluxxor, Delorean, Tuxedo, the list goes on...) which implement the concepts. We evaluated a few of them but for one reason or another were unable to find a good fit. In our case, flexibility is of the utmost importance. HubSpot’s marketing and sales products are made up of over 50 separate Backbone "micro" applications (alongside hundreds of microservices). Consequently, a rewrite of our entire codebase is out of the question. Instead, we'll migrate our apps incrementally, with a hybrid phase when data is shared between Backbone and Flux.

Flux

Since React is data agnostic, it’s possible (maybe even easy) to incrementally build React subtrees within an existing application. We need that same agility from Flux. We need to build stores based on Backbone Models shared with legacy sections of the UI now, and remove the models in favor of vanilla JS objects (or possibly immutable data structures) later.

Introducing the general-store

GeneralStore

general-store aims to provide all the features of a Flux store without prescribing the implementation of that store's data or mutations.

Briefly, a store:

  • contains any arbitrary value
  • exposes that value via a get method
  • responds to specific events from the dispatcher
  • notifies subscribers when its value changes

That's it. All other features, like Immutability, data fetching, undo, etc. are implementation details. We use a builder pattern to define stores and encapsulate a store’s data within a function closure.

function defineUserStore() {
  var users = [];
  return GeneralStore.define()
    .defineGet(function() {
      return users;
    })
    .defineResponseTo('ADD_USER', function(user) {
      users.push(user);
    })
    .register(dispatcherInstance);
}

var UserStore = defineUserStore();
UserStore.addOnChange(function() {
  console.log('users changed:', UserStore.get());
});

general-store also ships with a sweet mixin for declaratively expressing a React component’s store dependencies.

var UserListComponent = React.createClass({
  mixins: [
    GeneralStore.StoreDependencyMixin({
      users: UserStore
    })
  ],
  render: function() {
    return (
      <ul>
        {this.state.users.map(user => <li>{user.name}</li>)}
      </ul>
    );
  }
});

Learn more

To learn more about general-store and the React mixin visit HubSpot/general-store on Github.

Install general-store with npm (npm install general-store) or bower (bower install general-store).

Or download the source from Github.

 

 

 

Colby Rabideau

Written by Colby Rabideau

Subscribe for updates

    New Call-to-action