Skip to content

Instantly share code, notes, and snippets.

@gpbl
Last active January 11, 2024 06:05
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save gpbl/b17d34783722e8120a1f to your computer and use it in GitHub Desktop.
Save gpbl/b17d34783722e8120a1f to your computer and use it in GitHub Desktop.
Fluxible Store for keeping track of loading and error states for any payload (usage example in comments)
import createStore from 'fluxible/utils/createStore';
import hashObject from 'hash-object';
import { omit } from 'lodash';
const debug = require('debug')('App:StatusStore');
const StatusStore = createStore({
storeName: 'StatusStore',
handlers: {
LOAD_START: 'onLoadStart',
LOAD_SUCCESS: 'onLoadSuccess',
LOAD_FAILURE: 'onLoadFailure'
},
_getHash(obj) {
const hash = hashObject(obj);
return hash;
},
_setStatus(payload, isLoading, isLoaded, error) {
const status = {
isLoading: !!isLoading,
isLoaded: !isLoading && isLoaded && !error,
error: error
};
const hash = this._getHash(payload);
this.status[hash] = status;
debug('Assigned status with hash %s', hash, status, payload);
},
initialize() {
this.status = {};
},
onLoadStart(payload) {
this._setStatus(payload, true, false, null);
this.emitChange();
},
onLoadSuccess(payload) {
this._setStatus(payload, false, true, null);
this.emitChange();
},
onLoadFailure(payload) {
if (!payload.error)
console.warn('Missing error property in payload');
this._setStatus(omit(payload, 'error'), false, false, payload.error);
this.emitChange();
},
get(payload) {
const hash = this._getHash(payload);
if (!this.status[hash]){
debug('Cannot find status for hash %s, setting payload as not loaded', hash, payload);
this._setStatus(payload, false, true, null);
}
return this.status[hash];
},
dehydrate() {
return this.status;
},
rehydrate(state) {
this.status = state;
}
});
export default StatusStore;
@gpbl
Copy link
Author

gpbl commented Feb 8, 2015

Example of an action creator dispatching LOAD_* events:

// action creator
// params could be:
//   {}     (empty) all the books
//   { page: 1 } for pagination
//   { id: 1 } for resource with id = 1
//   { q: 'keyword' } for searching
export function loadBook(context, params, done) {

  // payload for the status store
  var payload = Object.assign({}, params, { resource: 'books' });

  context.dispatch('LOAD_START', payload);

  context.service.read('books', params, {}, (err, data) => {
    if (err) {
      payload.error = err; // needed to register the error
      context.dispatch('LOAD_FAILURE', payload);
      return done(err);
    }
    context.dispatch('LOAD_BOOK_SUCCESS', data);
    context.dispatch('LOAD_SUCCESS', payload);
    done();
  });
};

Example of a component getting the loading/error status from the StatusStore:

// Books.jsx
import React from 'react';
import { loadBooks } from '../actions/books';
import BooksStore from '../stores/BooksStore';
import StatusStore from '../stores/StatusStore';
const Books = React.createClass({

  mixin: [FluxibleMixin],

  statics: {
    listeners: [StatusStore, BooksStore],
    fetchData(context, params, query, done) {
      // used by react-router
      context.executeAction(loadBooks, params, done);
    }
  },
  componentDidMount() {
    if (!this.state.status.isLoaded) {
      this.executeAction(loadBooks, params, done);
    }
  },
  getInitialState() {
    return this.getStatesFromStores();
  },
  getStatesFromStores() {
    return {
      books: this.getStore(BooksStore).get(),
      status: this.getStore(StatusStore).get({resource: 'books'})
    }
  }, 
  onChange() {
    this.setState(this.getStatesFromStores());
  },

  render() {
    const { books, status } = this.state;
    if (status.isLoading || !status.isLoaded) {
      return <p>Loading...</p>
    }
    if (status.error) {
      return <p>Error! {status.error.message}</p>
    }
    return (
      <div>
        { this.state.status.isLoading  }
      </div>
    );
  }

});

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