Stores API

var UsersStore = Marty.createStore({
  id: 'UsersStore',
  handlers: {
    addUser: Constants.RECEIVE_USER
  },
  getInitialState: function () {
    return [];
  },
  addUser: function (user) {
    this.state.push(user);
    this.hasChanged();
  }
});
class UsersStore extends Marty.Store {
  constructor(options) {
    super(options);
    this.state = [];
    this.handlers = {
      addUser: Constants.RECEIVE_USER
    };
  }
  addUser(user) {
    this.state.push(user);
    this.hasChanged();
  }
}

id

A unique identifier (required). Needed for and (de)hydration and by the registry to uniquely identify the type.

displayName

An (optional) display name for the action creator. Used for richer debugging. We will use the Id if displayName hasn't been set. If you're using ES6 classes, displayName will automatically be the name of the class.

for(obj)

Resolves the instance of the object for the objects Marty context. The context can either be the object itself or available at obj.context or obj.context.marty.

handlers

The handlers property is used to define which handlers should be called when an action is dispatched. The key is the name of the handler and value is an action predicate.

When invoked the handlers arguments are the arguments passed to the dispatcher. The original action is available by calling this.action.

var UsersStore = Marty.createStore({
  id: 'UsersStore'
  handlers: {
    addUser: Constants.RECEIVE_USER
  },
  getInitialState: function () {
    return {};
  },
  addUser: function (user) {
    console.log(this.action) // { type: 'RECEIVE_USER', arguments: [{ name: ...}] }
    ...
  }
});
class UsersStore extends Marty.Store {
  constructor(options) {
    super(options);
    this.state = {};
    this.handlers = {
      addUser: Constants.RECEIVE_USER
    };
  }
  addUser: function (user) {
    console.log(this.action) // { type: 'RECEIVE_USER', arguments: [{ name: ...}] }
    ...
  }
}

Action predicates

An action predicate can either be a single value or an array of either action types (i.e. a strong) or a where query. Some examples of action predicates:

var UsersStore = Marty.createStore({
  id: 'UserStore',
  handlers: {
    foo: UserConstants.ADD_USER,
    bar: [UserConstants.ADD_USER, 'UPDATE_USER']
  },
  // called when action.type == 'ADD_USER'
  foo: function () { .. },

  // called when action.type == 'ADD_USER' || action.type ==  'UPDATE_USER'
  bar: function () { .. }
});
class UsersStore extends Marty.Store {
  constructor(options) {
    super(options);
    this.handlers = {
      foo: UserConstants.ADD_USER,
      bar: [UserConstants.ADD_USER, 'UPDATE_USER']
    };
  }
  // called when action.type == 'ADD_USER'
  foo() { .. },

  // called when action.type == 'ADD_USER' || action.type ==  'UPDATE_USER'
  bar() { .. }
}

Rollback

There are a number of cases where it would be useful to be able to rollback an action (e.g. if you've optimistically added an entity to your locally store but the associated request to the server failed).

To provide a rollback to an action handler, simply return a function from the action handler. If an action is rolled back, the function you return will be called.

var UsersStore = Marty.createStore({
  id: 'UserStore',
  handlers: {
    addUser: Constants.RECEIVE_USER
  },
  getInitialState: function () {
    return {};
  },
  addUser: function (user) {
    this.state.push(user);
    this.hasChanged();

    return function rollback() {
      this.state.splice(this.state.indexOf(user), 1);
      this.hasChanged();
    }
  }
});
class UsersStore extends Marty.Store {
  constructor(options) {
    super(options);
    this.state = {};
    this.handlers = {
      addUser: Constants.RECEIVE_USER
    };
  },
  addUser(user) {
    this.state.push(user);
    this.hasChanged();

    return function rollback() {
      this.state.splice(this.state.indexOf(user), 1);
      this.hasChanged();
    }
  }
}

getInitialState()

Classic

getInitialState (required) is called when the store is first instantiated. It expects you to pass an object back which represents the stores state. The value you return will subsequently be available from the state property.

ES6

getInitialState is no longer necessary, instead you should set the state in the constructor.

state

The state property holds the current state of the store. You can get the state by calling this.state or this.getState().

If you want to change the state to a new instance (or if you are using immutable data collections) you can set the states value.

addUsers: function (users) {
  this.state = this.state.concat(users);
}
addUsers(users) {
  this.state = this.state.concat(users);
}

If the new state does not equal the current state then hasChanged will be called after updating the stores state.

replaceState(nextState)

Replace the nextState with the current state. Same as setting this.state. Triggers hasChanged if the new state is different.

addUsers: function (users) {
  this.replaceState(_.extend(users, this.state));
}
addUsers(users) {
  this.replaceState(_.extend(users, this.state));
}

setState(nextState)

Merges nextState with the current state. Triggers hasChanged.

addUser: function (user) {
  var nextState = {};
  nextState[user.id] = user;
  this.setState(nextState);
}
addUser(user) {
  this.setState({
    [user.id]: user
  });
}

addChangeListener(callback, [context])

If you want to be notified of changes to a store, you can register a callback by calling Store.addChangeListener. You can optionally pass the functions context as a second argument.

Once registered, the store will invoke your callback anytime the store changes passing the state and the store as the two arguments.

addChangeListener will return a disposable object. If you want to stop listening to the store, you can call dispose on the object.

var listener = UserStore.addChangeListener(function (state, store) {
  console.log('state', state);
  console.log('from', store.displayName);
}, this);

listener.dispose();

hasChanged()

Calls any [registered callbacks](#addChangeListener). To improve performance listeners will only be notified once for each dispatched action.
var UsersStore = Marty.createStore({
  id: 'UserStore',
  handlers: {
    addUser: Constants.RECEIVE_USER
  },
  addUser: function (user) {
    this.state.push(user);
    this.hasChanged();
  }
});
class UsersStore extends Marty.Store {
  constructor(options) {
    super(options);
    this.state = {};
    this.handlers = {
      addUser: Constants.RECEIVE_USER
    };
  }
  addUser(user) {
    this.state.push(user);
    this.hasChanged();
  }
}

fetch(options)

When requesting data from a store we should assume that it might require an async operation. Store#fetch provides a simple syntax that allows you to encapsulate that asynchronicity in a flux way. The fetch function allows you to specify how to get the state locally and remotely and returns a fetch result which represents the current state of the fetch.

var UsersStore = Marty.createStore({
  id: 'UserStore',
  getUser: function (id) {
    return this.fetch({
      id: api-id,
      locally: function () {
        return this.state[id];
      },
      remotely: function () {
        return UserAPI.getUser(id);
      }
    });
  }
});

UsersStore.getUser(123).when({
  pending: function () {
    return <div className="pending"/>;
  },
  failed: function (error) {
    return <div className="error">{error.message}</div>;
  },
  done: function (user) {
    return <div className="user">{user.name}</div>;
  }
});
class UsersStore extends Marty.Store {
  getUser(id) {
    return this.fetch({
      id: id,
      locally() {
        return this.state[id];
      },
      remotely() {
        return UserAPI.getUser(id);
      }
    });
  }
}

UsersStore.getUser(123).when({
  pending() {
    return <div className="pending"/>;
  },
  failed(error) {
    return <div className="error">{error.message}</div>;
  },
  done(user) {
    return <div className="user">{user.name}</div>;
  }
});

If you need to wait for multiple fetch results to be finished, you can use when.all(). You pass in an array of fetch results and it will execute the appropriate handler (e.g. if any of the handlers failed then it will execute the failed handler).

var when = require('marty/when');

var foo = FooStore.getFoo(123);
var bar = BarStore.getBar(456);

when.all([foo, bar], {
  pending: function () {
    return <div className="pending"/>;
  },
  failed: function (error) {
    return <div className="error">{error.message}</div>;
  },
  done: function (user) {
    return <div className="user">{user.name}</div>;
  }
});

Options

Name type required description
id number | string | object true Uniquely identifies the bit of state you are fetching. Only one request for a given Id can be in progress at any time. If another request is in progress then a pending fetch result is returned
locally function true Should try and fetch from the local state. If it returns a value then an done fetch result will be returned immediately. If it returns undefined then the remotely function will be called. If it returns null then it will return a not found fetch result.
remotely function false If ``locally`` returned undefined then ``remotely`` is invoked. When ``remotely`` has finished then ``locally`` will be reinvoked and should contain now contain the state. If ``remotely`` returns a promise then ``locally`` will be called if the promise is fulfilled
dependsOn fetch result | array of fetch results false If a fetch depends on some other state being there already you can pass in their fetch results. It will only try and start fetching the data when all dependencies are done.
cacheError boolean false If true (default) then if an error is thrown at any point during a fetch then that error is cached and returned immediately instead.
Often you're only concerned with fetching locally and remotely so you use the second function signature ``fetch(id, locally, remotely)``:
var UsersStore = Marty.createStore({
  id: 'UserStore',
  getUser: function (id) {
    return this.fetch(id,
      function () {
        return this.state[id];
      },
      function () {
        return UserQueries.getUser(id);
      }
    });
  }
});
class UsersStore extends Marty.Store {
  getUser(id) {
    return this.fetch(id,
      () => return this.state[id],
      () => return UserQueries.getUser(id)
    );
  }
});

Fetch Result

Fetch returns a result object that represents the current state of the fetch. It has a status which can be either PENDING, DONE OR FAILED. You can get the status by accessing fetch.status or with the helpers fetch.pending, fetch.failed or fetch.done.

A fetch result is not a promise. It is an object literal that represents the fetch at the point the fetch was invoked. If the state of the fetch changes you will have to re-invoke fetch to get the updated state (e.g. After a store changes).
var user = UserStore.getUser(id);

console.log(user.status) // => PENDING
If the fetch has an error, then the error is accessible at ``result#error``
if (user.failed) {
  console.log('failed to get user', user.error);
}
If the fetch is done, then the result is accessible at ``result#result``
if (user.done) {
  console.log('got user', user.result);
}
If you start a new fetch then the store will notify any listeners when the fetch's status has changed.
UserStore.addChangeListener(function () {
  var user = UserStore.getUser(id);

  if (user.done) {
    console.log(user.result);
  }
});

when(handlers, [context])

when is a helper function that makes it easier to map the various states a fetch result can be in. You can pass in an optional context as the second argument which the handlers inherit from (allowing you to call other handlers or anything on the passed in context).
var component = user.when({
  pending: function () {
    return <Loading />;
  },
  failed: function (error) {
    return <ErrorPage error={error} />;
  },
  done: function (user) {
    return <div className="user">{user.name}</div>;
  }
}, this);

when.all([fetchResult*], handlers, [context])

when.all will wait for all fetch results to be done before invoked the done status handler. If any fetch result is pending or failed then the pending or failed status handlers will be invoked instead. If all fetch results are done then their results are passed to the done handler in an array. If any fetch result has failed then the error of the first failed fetch result will be passed to the failed status handler.
var when = require('marty/when');
var fetch = require('marty/fetch');

when.all([fetch.done("foo"), fetch.done("bar")], {
  pending: function () {
    console.log("pending");
  },
  done: function (results) {
    console.log("all done", results); // foo, bar
  },
  failed: function (error) {
    console.log("failed", error); // first error
  }
})

when.join(fetchResult*, handlers, [context])

when.join will wait for all fetch results to be done before invoked the done status handler. If any fetch result is pending or failed then the pending or failed status handlers will be invoked instead. If all fetch results are done then their results are passed to the done handler in an array. If any fetch result has failed then the error of the first failed fetch result will be passed to the failed status handler.
var when = require('marty/when');
var fetch = require('marty/fetch');

when.join(fetch.done("foo"), fetch.done("bar"), {
  pending: function () {
    console.log("pending");
  },
  done: function (results) {
    console.log("all done", results); // foo, bar
  },
  failed: function (error) {
    console.log("failed", error); // first error
  }
})

toPromise()

Converts a fetch result into a promise. Useful when you want to use a store outside of a React component.
var getUser = UserStore.getUser().toPromise();

getUser
  .then((user) => console.log(user))
  .catch((error) => console.error(error));

fetch.pending()

Returns a pending fetch result
var fetch = Store.fetch.pending();

// or
var fetch = require('marty/fetch').pending();

console.log(fetch.status) // PENDING

fetch.done(result)

Returns a done fetch result
var fetch = Store.fetch.done(result);

// or
var fetch = require('marty/fetch').done(result);

console.log(fetch.status, fetch.result) // DONE, { ... }

fetch.failed(error)

Returns a failed fetch result
var fetch = Store.fetch.failed(error);

// or
var fetch = require('marty/fetch').failed(error);

console.log(fetch.status, fetch.error) // FAILED, { ... }

fetch.notFound()

Returns a failed fetch result with a NotFound error
var fetch = Store.fetch.notFound();

// or
var fetch = require('marty/fetch').notFound();

console.log(fetch.failed, fetch.error) // FAILED, { status: 404 }

hasAlreadyFetched(fetchId)

For when you want to know if you have already tried fetching something. Given a fetch Id it will return true or false depending on whether you have previously tried a fetch for that Id (Irrelevant of whether it was successful or not). Useful if you want to distinguish between an empty collection and needing to fetch state from a remote source.
var UsersStore = Marty.createStore({
  id: 'UsersStores',
  getUsers: function () {
    return this.fetch(
      id: 'users',
      locally: function () {
        if (this.hasAlreadyFetched('users')) {
          return this.state;
        }
      },
      remotely: function () {
        return UsersHttpAPI.getAll()
      }
    })
  }
});
class UsersStore extends Marty.Store {
  getUsers() {
    return this.fetch(
      id: 'users',
      locally() {
        if (this.hasAlreadyFetched('users')) {
          return this.state;
        }
      },
      remotely() {
        return UsersHttpAPI.getAll()
      }
    });
  }
}

dehydrate()

Should return the state of your store in a form that can be serialised to JSON. Used in conjunction with rehydrate for synchronising the state of a store on the server with its browser counterpart. If not implemented then Marty will attempt to serialise this.state.

rehydrate(dehydratedState)

Expects the store to deserialize the dehydrated state and initialise the store. Used in conjunction with dehydrate for synchronising the state of a store on the server with its browser counterpart. If not implemented then Marty will call this.replaceState(dehydratedState).

waitFor(*stores)

If an action handler is dependant on another store having already processed the action they can wait for those stores to finish processing by calling waitFor. If you call waitFor with the stores you wish to wait for (or pass an array), it will stop execution of the current action handler, process all dependent action handlers and then continue execution of the original action handler.

You can also pass an array of dispatch tokens to waitFor.

var UsersStore = Marty.createStore({
  id: 'UsersStore',
  handlers: {
    addUser: Constants.RECEIVE_USER
  },
  addUser: function (user) {
    this.waitFor(FooStore, BarStore);
    this.state.push(user);
    this.hasChanged();
  }
});
class UsersStore extends Marty.Store {
  constructor(options) {
    super(options);
    this.handlers = {
      addUser: Constants.RECEIVE_USER
    };
  }
  addUser(user) {
    this.waitFor(FooStore, BarStore);
    this.state.push(user);
    this.hasChanged();
  }
}

dispatchToken

Dispatch token that is returned from Dispatcher.register(). Used by waitFor().