Store
Create stores by extending from the base Store
class.
class MessageStore extends Store {
// Note that passing a flux instance to the constructor is not required;
// we do it here so we have access to any action ids we're interested in.
constructor(flux) {
// Don't forget to call the super constructor
super();
// Register handlers with the dispatcher using action ids, or the
// actions themselves
const messageActions = flux.getActions('messages');
this.register(messageActions.newMessage, this.handleNewMessage);
// Set initial state using assignment
this.state = {};
}
// Define handlers as methods...
handleNewMessage() { ... }
// It's also useful to define accessor-style methods that can be used by
// your views on state changes
getMessage(id) { ... }
}
Managing state
In a Flux application, all application state is contained inside stores, and the state of a store can only be changed by the store itself. The sole point of entry for data in a store is via the dispatcher, but information sent through the dispatcher should be thought of not as data, but as messages describing data. This may seem like a distinction without a difference, but by sticking to this concept, it ensures that your stores remain isolated and free of the complicated dependencies you often find in MVC-style apps.
Because state is so crucial, Flummox is a little more opinionated than some other Flux libraries about how to manage it: for instance, all state must be stored in the this.state
object. That being said, Flummox places no restrictions on the types of data you store, so interoperability with libraries like Immutable.js is a non-issue. If this sounds familiar, it’s because the store API is heavily influenced by the React component API:
- All state mutations must be made via
setState()
. this.state
is used to access the current state, but it should never be mutated directly. Treatthis.state
as if it were immutable.- As in React +0.13, initial state is set by assignment in the constructor.
Performing optimistic updates
A common pattern when performing server operations is to update the application’s UI optimistically — before receiving a response from the server — then responding appropriately if the server returns an error. Use the method registerAsync
to register separate handlers for the beginning of an asynchronous action and on success and failure. See below for details.
Customizing top-level state type
The default top-level state type (this.state
) is a plain object. You can add any type of data to this structure, as in React. However, if you want even more control over state management, you can customize the top-level state type by overriding the static Store method Store.assignState()
. This method is used internally to perform state changes. The default implementation is essentially a wrapper around Object.assign()
:
// Default implementation
static assignState(oldState, newState) {
return Object.assign({}, oldState, newState);
}
Things to keep in mind when overriding assignState()
:
- Should be non-mutative.
assignState(null, newState)
should not throw and should return a copy ofnewState
.
To support React integration with FluxComponent, you should also override Store#getStateAsObject()
, which returns a plain object representation of this.state
. The default state getter uses the object returned by this function.
Methods
register
register(function action | string actionId , function handler)
Register a handler for a specific action. The handler will be automatically bound to the store instance.
You can register using either the action id or the action itself.
Usage note: register()
works for both async and sync actions. In the case of async actions, it receives the resolved value of the promise returned by the action.
registerAsync
registerAsync(function action | string actionId [, function begin, function success, function failure])
A register handler specifically for asynchronous actions (actions that return promises).
beginHandler
is called at the beginning of the asynchronous action. It receives same arguments that were passed to the action.successHandler
works the same as if you registered an async action withregister()
: it is called if and when the asynchronous action resolves. It receives the resolved value of the promise returned by the action.failureHandler
is called if and when the asynchronous action is rejected. It receives the rejected value of the promise returned by the action (by convention, an error object).
This makes it easy perform to perform optimistic UI updates.
If any of the passed handlers are not functions, they are ignored.
Usage note: registerAsync(null, handler, null)
is functionally equivalent to register(handler)
. If you don’t need to respond to the beginning of an async action or respond to errors, then just use register()
.
setState
setState(function|object nextState)
Shallow merges nextState
with the current state, then emits a change event so views know to update themselves.
Similar to React, multiple calls to setState()
within the same handler are batched and applied at the end. Accessing this.state
after calling setState()
will return the existing value, not the updated value.
You can also do transactional state updates by passing a function:
this.setState(state => ({ counter: state.counter + 1 }));
replaceState
replaceState(object nextState)
Like setState()
but deletes any pre-existing state keys that are not in nextState.
forceUpdate
forceUpdate()
Emits change event.
Usage note: If you can, use setState()
instead.
EventEmitter methods
Flummox stores are EventEmitters — specifically eventemitter3 — so you can use any of the EventEmitter methods, the important ones being addListener()
and removeListener()
. Use these in your controller-views to subscribe to changes.
Usage note: A change
event is emitted automatically whenever state changes. Generally, this is the only event views should need to subscribe to. Unlike in MVC, Flux store events don’t pass data around to different parts of your application; they merely broadcast that a change has occurred within a store, and interested parties should synchronize their state accordingly.
Dispatcher methods
waitFor
waitFor(Store store)
Within a handler, this waits for a different store to respond to the dispatcher before continuing. The operation is synchronous. E.g.
someActionHandler() {
this.waitFor(someStore);
// someStore has completed, continue...
}
Internally, it calls Dispatcher#waitFor()
.
Instead of passing a store, you can also pass a dispatcher token, or an array of tokens and stores.
Usage note: Because this method introduces dependencies between stores, you should generally try to avoid using it. Stores should be as isolated as possible from the outside world. If you find yourself relying on waitFor()
often, consider rethinking how data flows through your app.
Static Methods
serialize(state)
If you use Flux.serialize
, Flummox will try to call the static method serialize
on all your stores. Flummox will pass the state object of the store to the method and expects a String
deserialize(state)
If you use Flux.deserialize
, Flummox will try to call the static method deserialize
on all your stores. Flummox will pass the appropriate serialized representation and expects an object, with which Flummox will call replaceState
on your store.