Skip to content

Instantly share code, notes, and snippets.

@thomasboyt
Created October 22, 2014 15:09
Show Gist options
  • Star 11 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save thomasboyt/5a2fda0db3af7d93c8b6 to your computer and use it in GitHub Desktop.
Save thomasboyt/5a2fda0db3af7d93c8b6 to your computer and use it in GitHub Desktop.
Flux loading state pattern

I've been struggling to come up with a good pattern for handling loading state in Flux (specifically using Fluxxor, though I think this is an issue for any implementation).

When I say "loading state," what I mean is state in a store that tracks:

  • Whether the data handled by the store was loaded
  • Whether the store is currently attempting to load data
  • Whether the data loaded successfully or errored
  • The error message, if it errored

Here's my first (very simple) pass at this, a store mixin called LoadingStoreMixin.js:

module.exports = {
  // Call in the constructor and any time the data is to be "reset"
  initLoadingState: function() {
    this.loaded = false;
    this.loading = false;
    this.didError = false;
    this.error = null;
  },

  // Call when loading is begun (e.g. onLoadStart)
  startLoading: function() {
    this.loaded = false;
    this.loading = true;
    this.didError = false;
    this.error = null;
  },

  // Call when loading succeeds (e.g. onLoadSuccess)
  finishLoading: function() {
    this.loaded = true;
    this.loading = false;
    this.didError = false;
    this.error = null;
  },

  // Call when loading fails (e.g. onLoadErrror)
  errorLoading: function(err) {
    this.loaded = false;
    this.loading = false;
    this.didError = true;
    this.error = err;
  }
};

(Fluxxor doesn't yet support mixins out of the box, but it's easy enough to fudge with e.g. _.merge on the hash passed to Fluxxor.createStore).

The intended usage is to manually call these functions from within your load messages:

var ContactsActions = {
  load: function() {
    this.dispatch('contacts:load');

    someAjax({
      url: '/contacts',
      method: 'GET'
    }).then((resp) => {
      this.dispatch('contacts:loadSuccess', resp);
    }, (err) => {
      this.dispatch('contacts:loadError', err);
    });
  }
};

var ContactsStore = Fluxxor.createStore(_.merge({
  initialize: function() {
    this.contracts = null;
    this.initLoadingState();

    this.bindActions(
      'contacts:load', 'onLoadContacts',
      'contacts:loadSuccess', 'onLoadContactsSuccess',
      'contacts:loadError', 'onLoadContactsError'
    );
  },

  onLoadContacts: function() {
    this.startLoading();
    this.emit('change');
  },

  onLoadContactsSuccess: function(resp) {
    this.finishLoading();
    this.contacts = resp.data;
    this.emit('change');
  },

  onLoadContactsError: function(err) {
    this.errorLoading(err);
    this.emit('change');
  }
}, LoadingStateMixin);

Then, your component can use the loading, loaded, didError, and error attributes as expected.

There's a lot of limitations this mixin has (for example: it can't really handle >1 loadable resource at a time :<), but I feel like it's better than nothing. Ideally, Flux implementations like Fluxxor will start including more robust versions of patterns like this to ease some of the initial pains of boilerplate, as well as to provide best practices for storing data and state.

@zackify
Copy link

zackify commented Oct 31, 2014

Interesting, this looks cool. I like the idea.

@gaearon
Copy link

gaearon commented Oct 31, 2014

In my vanilla Flux boilerplate example, I store all lists fetched from server in so-called PaginatedList that also keeps track of current page number and loading state.

It is used from stores in this manner.

@plaxdan
Copy link

plaxdan commented Oct 31, 2014

One alternative is to have a single attribute for the state rather than having to coordinate multiple. For example:

Loadable = {
  // Call in the constructor and any time the data is to be "reset"
  init: function() {
    this.status = Constants.UNKNOWN
  },

  updateStatus: function(newStatus) {
    // validate status then.... 
    this.status = newStatus;
  }
};
module.exports = Loadable


var ContactsActions = {
  load: function() {
    this.dispatch('contacts:load');

    someAjax({
      url: '/contacts',
      method: 'GET'
    }).then((resp) => {
      this.dispatch('contacts:loadSuccess', resp);
    }, (err) => {
      this.dispatch('contacts:loadError', err);
    });
  }
};

var ContactsStore = Fluxxor.createStore(_.merge({
  initialize: function() {
    this.contracts = null;
    this.init();

    this.bindActions(
      'contacts:load', 'onLoadContacts',
      'contacts:loadSuccess', 'onLoadContactsSuccess',
      'contacts:loadError', 'onLoadContactsError'
    );
  },

  onLoadContacts: function() {
    this.startLoading();
    this.updateStatus(Constants.LOADING);
    this.emit('change');
  },

  onLoadContactsSuccess: function(resp) {
    this.contacts = resp.data;
    this.updateStatus(Constants.LOADED);
    this.emit('change');
  },

  onLoadContactsError: function(err) {
    this.updateStatus(Constants.ERROR);
    this.emit('change');
  }
}, Loadable);

@briandipalma
Copy link

What I do is move all the subscription logic out to a Utilities class, the ActionCreator calls it but it deals with all API communications. It means the Store and the View have no idea where loading state data come from so no need for mixins etc.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment