Component = function (props, context, updater) { _extend.call(this, props, context, updater); }
n/a
PureComponent = function (props, context, updater) { _extend.call(this, props, context, updater); }
n/a
Store = function () { // extending doesn't really work well here, so instead we create an internal instance // and just loop through its properties/methods and make a getter/setter for each // that will actually be getting and setting on that internal instance. this.__store__ = Reflux.createStore(); this.state = {}; var self = this; for (var key in this.__store__) { /*jshint loopfunc: true */ (function (prop) { Object.defineProperty(self, prop, { get: function () { return self.__store__[prop]; }, set: function (v) { self.__store__[prop] = v; } }); })(key); } }
n/a
all = function () /* listenables... */{ var listenables = slice.call(arguments); return (0, _createStore.createStore)({ init: function init() { this[strategyMethodNames[strategy]].apply(this, listenables.concat("triggerAsync")); } }); }
n/a
connect = function (listenable, key) { _.throwIf(typeof(key) === 'undefined', 'Reflux.connect() requires a key.'); return { getInitialState: function() { if (!_.isFunction(listenable.getInitialState)) { return {}; } return _.object([key],[listenable.getInitialState()]); }, componentDidMount: function() { var me = this; _.extend(me, ListenerMethods); this.listenTo(listenable, function(v) { me.setState(_.object([key],[v])); }); }, componentWillUnmount: ListenerMixin.componentWillUnmount }; }
...
You can have multiple calls to `Reflux.listenTo` in the same `mixins` array.
There is also `Reflux.listenToMany` which works in exactly the same way, exposing `listener.listenToMany`.
#### Using Reflux.connect
If all you want to do is update the state of your component to whatever the data store transmits, you can use `Reflux.connect(listener,stateKey)` as a mixin. The state is updated via `this.setState({<stateKey
x3e;:data})`. Here's the example above changed to use this syntax:
```javascript
var Status = React.createClass({
mixins: [Reflux.connect(statusStore,"currentStatus")],
render: function() {
// render using `this.state.currentStatus`
}
...
connectFilter = function (listenable, key, filterFunc) { _.throwIf(_.isFunction(key), 'Reflux.connectFilter() requires a key.'); return { getInitialState: function() { if (!_.isFunction(listenable.getInitialState)) { return {}; } // Filter initial payload from store. var result = filterFunc.call(this, listenable.getInitialState()); if (typeof(result) !== 'undefined') { return _.object([key], [result]); } else { return {}; } }, componentDidMount: function() { var me = this; _.extend(this, ListenerMethods); this.listenTo(listenable, function(value) { var result = filterFunc.call(me, value); me.setState(_.object([key], [result])); }); }, componentWillUnmount: ListenerMixin.componentWillUnmount }; }
...
`connectFilter` mixin when you want only a subset of the items in a store. A
blog written using Reflux would probably have a store with all posts in
it. For an individual post page, you could use `Reflux.connectFilter` to
filter the posts to the post that's being viewed.
```javascript
var PostView = React.createClass({
mixins: [Reflux.connectFilter(postStore, "post", function(posts) {
return posts.filter(function(post) {
return post.id === this.props.id;
}.bind(this))[0];
})],
render: function() {
// render using `this.state.post`
}
...
function createAction(definition) {
definition = definition || {};
if (!_.isObject(definition)) {
definition = { actionName: definition };
}
for (var a in ActionMethods) {
if (!allowed[a] && PublisherMethods[a]) {
throw new Error("Cannot override API method " + a + " in Reflux.ActionMethods. Use another method name or override it
on Reflux.PublisherMethods instead.");
}
}
for (var d in definition) {
if (!allowed[d] && PublisherMethods[d]) {
throw new Error("Cannot override API method " + d + " in action creation. Use another method name or override it on
Reflux.PublisherMethods instead.");
}
}
definition.children = definition.children || [];
if (definition.asyncResult) {
definition.children = definition.children.concat(["completed", "failed"]);
}
var i = 0,
childActions = {};
for (; i < definition.children.length; i++) {
var chDef = definition.children[i];
var chName = typeof chDef === "string" ? chDef : chDef.actionName;
childActions[chName] = createAction(chDef);
}
var context = _.extend({
eventLabel: "action",
emitter: new _.EventEmitter(),
_isAction: true
}, PublisherMethods, ActionMethods, definition);
var functor = function functor() {
var hasChildActions = false;
/* eslint no-unused-vars:0 */
for (var ignore in functor.childActions) {
hasChildActions = true;break;
}
var async = !functor.sync && typeof functor.sync !== "undefined" || hasChildActions;
var triggerType = async ? "triggerAsync" : "trigger";
return functor[triggerType].apply(functor, arguments);
};
_.extend(functor, childActions, context);
Keep.addAction(functor);
return functor;
}
...
## Usage
### Creating actions
Create an action by calling `Reflux.createAction` with an optional options object.
```javascript
var statusUpdate = Reflux.createAction(options);
```
An action is a [function object](http://en.wikipedia.org/wiki/Function_object) that can be invoked like any function.
```javascript
statusUpdate(data); // Invokes the action statusUpdate
statusUpdate.triggerAsync(data); // same effect as above
...
createActions = function (definitions) { var actions = {}; if (definitions instanceof Array) { definitions.forEach(function (val) { if (_.isObject(val)) { reducer(val, actions); } else { actions[val] = (0, _createAction.createAction)(val); } }); } else { reducer(definitions, actions); } return actions; }
...
```
If `options.sync` is true, the functor will instead call `action.trigger` which is a synchronous operation. You can change `action
.sync` during the lifetime of the action, and the following calls will honour that change.
There is also a convenience function for creating multiple actions.
```javascript
var Actions = Reflux.createActions([
"statusUpdate",
"statusEdited",
"statusAdded"
]);
// Actions object now contains the actions
// with the names given in the array above
...
function createStore(definition) { definition = definition || {}; for (var a in StoreMethods) { if (!allowed[a] && (PublisherMethods[a] || ListenerMethods[a])) { throw new Error("Cannot override API method " + a + " in Reflux.StoreMethods. Use another method name or override it on Reflux.PublisherMethods / Reflux.ListenerMethods instead."); } } for (var d in definition) { if (!allowed[d] && (PublisherMethods[d] || ListenerMethods[d])) { throw new Error("Cannot override API method " + d + " in store creation. Use another method name or override it on Reflux .PublisherMethods / Reflux.ListenerMethods instead."); } } definition = (0, _mixer.mix)(definition); function Store() { var i = 0, arr; this.subscriptions = []; this.emitter = new _.EventEmitter(); this.eventLabel = "change"; (0, _bindMethods.bindMethods)(this, definition); if (this.init && _.isFunction(this.init)) { this.init(); } if (this.listenables) { arr = [].concat(this.listenables); for (; i < arr.length; i++) { this.listenToMany(arr[i]); } } } _.extend(Store.prototype, ListenerMethods, PublisherMethods, StoreMethods, definition); var store = new Store(); Keep.addStore(store); return store; }
...
### Creating data stores
Create a data store much like ReactJS's own `React.createClass` by passing a definition object to `Reflux.createStore`. You
may set up all action listeners in the `init` function and register them by calling the store's own `listenTo` function.
```javascript
// Creates a DataStore
var statusStore = Reflux.createStore({
// Initial setup
init: function() {
// Register statusUpdate action
this.listenTo(statusUpdate, this.output);
},
...
function defineReact(react, noLongerUsed, extend) { var proto, _extend; // if no Reflux object is yet available then return and just wait until defineReact is called manually with it try { _react = react || _react || React; _extend = extend || _react.Component; } catch (e) { return; } // if Reflux and React aren't present then ignore, wait until they are properly present // also ignore if it's been called before UNLESS there's manual extending happening if (!_react || !_extend || (_defined && !extend)) { return; } // ----------- BEGIN Reflux.Component ------------ /** * Reflux.Component: * An implementation for idiomatic React.js classes that mix with * Reflux stores. To utilize extend Reflux.Component instead of * React.Component. Then you may hook any Reflux store that has a * `this.state` property containing its state values to the component * via `this.store` or an Array of Reflux stores via `this.stores` in * the component's constructor (similar to how you assign initial state * in the constructor in ES6 style React). The default values of the * stores will automatically reflect in the component's state, and any * further `trigger` calls from that store will update properties passed * in the trigger into the component automatically. */ var RefluxComponent = function(props, context, updater) { _extend.call(this, props, context, updater); }; // equivalent of `extends React.Component` or other class if provided via `extend` param Reflux.utils.inherits(RefluxComponent, _extend); proto = RefluxComponent.prototype; /** * this.storeKeys * When this is a falsey value (null by default) the component mixes in * all properties from the stores attached to it and updates on changes * from all of them. When set to an array of string keys it will only * utilized state property names of those keys in any store attached. This * lets you choose which parts of stores update the component on a component- * by-component basis. If using this it is best set in the constructor. */ proto.storeKeys = null; // on the mounting of the component that is where the store/stores are attached and initialized if needed proto.componentWillMount = function () { // if there is a this.store then simply push it onto the this.stores array or make one if needed if (this.store) { if (Array.isArray(this.stores)) { this.stores.unshift(this.store); } else { this.stores = [this.store]; } } if (this.stores) { this.__storeunsubscribes__ = this.__storeunsubscribes__ || []; var sS = this.setState.bind(this); // this handles the triggering of a store, checking what's updated if proto.storeKeys is utilized var onStoreTrigger = function(obj){ var updateObj = filterByStoreKeys(this.storeKeys, obj); if (updateObj) { sS(updateObj); } }.bind(this); // for each store in this.stores... for (var i = 0, ii = this.stores.length; i < ii; i++) { var str = this.stores[i]; // if's a function then we know it's a class getting passed, not an instance if (typeof str === 'function') { var storeId = str.id; // if there is NOT a .singleton property on the store then this store has not been initialized yet, so do so if (!str.singleton) { str.singleton = new str(); if (storeId) { Reflux.stores[storeId] = str.singleton; } } // before we weren't sure if we were working with an instance or class, so now we know an instance is created set it // to the variables we were using so that we can just continue on knowing it's the instance we're working with this.stores[i] = str = str.singleton; // the instance should have an .id property as well if the class does, so set that here str.id = storeId; // if there is an id and there is a global state property for this store then merge // the properties from that global state into the default state of the store AND then ...
...
`Reflux.Component` extends `React.Component`. Therefore Reflux needs to be able to access React in order to expose it. In the browser
as long as React is loaded before Reflux then Reflux will automatically find it. Likewise in node-like environments where `require
('react')` will function Reflux will try to access React that way. So in almost all situations Reflux will find React
on its own.
However, Reflux also exposes the method `Reflux.defineReact` that you can use to manually give Reflux a reference to the React object
in case you need to:
```javascript
// only needed if, for some reason, Reflux can't get reference to React:
var React = /* however you access React */;
Reflux.defineReact(React);
// now Reflux.Component is accessible!
```
### Extending a 3rd Party Class
Sometimes 3rd party libraries will have their own class that extends `React.Component` that they require you to use. Reflux handles
this by exposing the `Reflux.Component.extend` method. If you have such a 3rd party class you can pass that class to this method
and it will return a version of `Reflux.Component` that extends it instead of extending `React.Component` directly. Example:
...
getGlobalState = function () { return clone(Reflux.GlobalState); }
...
// at this point it would render with a count of 50!
```
One of the most useful ways you could do this is to store a `Reflux.GlobalState` state as a JSON string in order to implement it
again the next time the app starts up and have the user begin right where they left off.
#### Reflux.setGlobalState and Reflux.getGlobalState
Directly accessing `Reflux.GlobalState` is a fine way to do set the starting state of an app and to do automated testing, but it
is also helpful to be able to manipulate the global state while the app is running as well. To do this Reflux exposes a `Reflux
.getGlobalState()` function and a `Reflux.setGlobalState()` function. The former allows
you to get a deep copy of the current global state (so that the copy will not mutate as the global state itself continues to mutate
) and the latter allows you to set part or all of the global state at any time in the program. Between these two functions things
like state time-travel, undo/redo, and move-by-move tracking become relatively easy.
### Making sure Reflux.Component is available
`Reflux.Component` extends `React.Component`. Therefore Reflux needs to be able to access React in order to expose it. In the browser
as long as React is loaded before Reflux then Reflux will automatically find it. Likewise in node-like environments where `require
('react')` will function Reflux will try to access React that way. So in almost all situations Reflux will find React
on its own.
However, Reflux also exposes the method `Reflux.defineReact` that you can use to manually give Reflux a reference to the React object
in case you need to:
...
initStore = function (str) { var storeId = str.id; // if they're initializing something twice then we're done already, return it if (str.singleton) { return str.singleton; } // if no id then it's easy: just make new instance and set to singleton if (!storeId) { str.singleton = new str(); return str.singleton; } // create the singleton and assign it to the class's singleton static property var inst = str.singleton = new str(); // store it on the Reflux.stores array to be accessible later Reflux.stores[storeId] = inst; // the singleton instance itself should also have the id property of the class inst.id = storeId; // if the global state has something set for this id, copy it to the state and then // make sure to set the global state to the end result, since it may have only been partial if (Reflux.GlobalState[storeId]) { for (var key in Reflux.GlobalState[storeId]) { inst.state[key] = Reflux.GlobalState[storeId][key]; } Reflux.GlobalState[storeId] = inst.state; // otherwise just set the global state to the default state of the class } else { Reflux.GlobalState[storeId] = inst.state; } // returns the singleton itself, though it will also be accessible as as `MyClass.singleton` return inst; }
...
proto.mapStoreToState = function(store, filterFunc)
{
// make sure we have a proper singleton instance to work with
if (typeof store === 'function') {
if (store.singleton) {
store = store.singleton;
} else {
store = Reflux.initStore(store);
}
}
// we need a closure so that the called function can remember the proper filter function to use, so function gets defined here
var self = this;
function onMapStoreTrigger(obj) {
// get an object
...
initializeGlobalStore = function (str) { var storeId = str.id; // if they're initializing something twice then we're done already, return it if (str.singleton) { return str.singleton; } // if no id then it's easy: just make new instance and set to singleton if (!storeId) { str.singleton = new str(); return str.singleton; } // create the singleton and assign it to the class's singleton static property var inst = str.singleton = new str(); // store it on the Reflux.stores array to be accessible later Reflux.stores[storeId] = inst; // the singleton instance itself should also have the id property of the class inst.id = storeId; // if the global state has something set for this id, copy it to the state and then // make sure to set the global state to the end result, since it may have only been partial if (Reflux.GlobalState[storeId]) { for (var key in Reflux.GlobalState[storeId]) { inst.state[key] = Reflux.GlobalState[storeId][key]; } Reflux.GlobalState[storeId] = inst.state; // otherwise just set the global state to the default state of the class } else { Reflux.GlobalState[storeId] = inst.state; } // returns the singleton itself, though it will also be accessible as as `MyClass.singleton` return inst; }
...
**Note!** `Reflux.Store` still works with instances of stores (i.e. the class must get intantiated). Assigning the class itself
to `this.store` just allows Reflux to handle the instantiation and do some internal things that allow features like global state
tracking. it does *not* mean that the class itself is the store. Internally Reflux creates and utilizes a singleton instance of
the class. After mounting you may access that singleton instance of the class via `MyStoreClass.singleton`.
#### Using Reflux.Store without a component
With to ability to do so much via global states (covered in the next section), and the fact that that functionality is tied to `
Reflux.Store`, being able to properly utilize `Reflux.Store` on its own (without binding to a React component) becomes useful. However
, just using `new MyStoreClass()` isn't enough, as it has to tie itself into the Reflux global state as a singleton. Therefore
Reflux exposes an API for getting a properly globalized singleton instance of a `Reflux.Store` extended class without having to
tie it to a React component. You do this via the following:
```javascript
var mySingleton = Reflux.initializeGlobalStore(MyClassName);
```
When done this way the singleton instance of your `Reflux.Store` class can, externally, be used much like a non-ES6 store created
via `Reflux.createStore` except with the advantages that it: 1) is written in the `Reflux.Store` ES6 syntax and 2) it ties in with
the global state being tracked by Reflux.
Note: your store _must_ be set up with an `id` to be used this way.
Note: even after instantiating with `Reflux.initializeGlobalStore` you can still later assign the class name itself to `this.store
` or `this.stores` in a `Reflux.Component`. The component will recognize that a singleton for the class has already been created
and use that singleton.
...
joinConcat = function () /* listenables... */{ var listenables = slice.call(arguments); return (0, _createStore.createStore)({ init: function init() { this[strategyMethodNames[strategy]].apply(this, listenables.concat("triggerAsync")); } }); }
n/a
joinLeading = function () /* listenables... */{ var listenables = slice.call(arguments); return (0, _createStore.createStore)({ init: function init() { this[strategyMethodNames[strategy]].apply(this, listenables.concat("triggerAsync")); } }); }
n/a
joinStrict = function () /* listenables... */{ var listenables = slice.call(arguments); return (0, _createStore.createStore)({ init: function init() { this[strategyMethodNames[strategy]].apply(this, listenables.concat("triggerAsync")); } }); }
n/a
joinTrailing = function () /* listenables... */{ var listenables = slice.call(arguments); return (0, _createStore.createStore)({ init: function init() { this[strategyMethodNames[strategy]].apply(this, listenables.concat("triggerAsync")); } }); }
...
#### Using the listener instance methods
All objects using the listener API (stores, React components using `ListenerMixin`, or other components using the `ListenerMethods
`) gain access to the four join instance methods, named after the argument strategy. Here's an example saving the last emission
from each publisher:
```javascript
var gainHeroBadgeStore = Reflux.createStore({
init: function() {
this.joinTrailing(actions.disarmBomb, actions.saveHostage, actions.recoverData
, this.triggerAsync);
}
});
actions.disarmBomb("warehouse");
actions.recoverData("seedyletter");
actions.disarmBomb("docks");
actions.saveHostage("offices",3);
...
listenTo = function (listenable, callback, initial){ return { /** * Set up the mixin before the initial rendering occurs. Import methods from `ListenerMethods` * and then make the call to `listenTo` with the arguments provided to the factory function */ componentDidMount: function() { for(var m in ListenerMethods){ if (this[m] !== ListenerMethods[m]){ if (this[m]){ throw "Can't have other property '"+m+"' when using Reflux.listenTo!"; } this[m] = ListenerMethods[m]; } } this.listenTo(listenable,callback,initial); }, /** * Cleans up all listener previously registered. */ componentWillUnmount: ListenerMethods.stopListeningToAll }; }
...
// Creates a DataStore
var statusStore = Reflux.createStore({
// Initial setup
init: function() {
// Register statusUpdate action
this.listenTo(statusUpdate, this.output);
},
// Callback
output: function(flag) {
var status = flag ? 'ONLINE' : 'OFFLINE';
// Pass on to listeners
...
listenToMany = function (listenables){ return { /** * Set up the mixin before the initial rendering occurs. Import methods from `ListenerMethods` * and then make the call to `listenTo` with the arguments provided to the factory function */ componentDidMount: function() { for(var m in ListenerMethods){ if (this[m] !== ListenerMethods[m]){ if (this[m]){ throw "Can't have other property '"+m+"' when using Reflux.listenToMany!"; } this[m] = ListenerMethods[m]; } } this.listenToMany(listenables); }, /** * Cleans up all listener previously registered. */ componentWillUnmount: ListenerMethods.stopListeningToAll }; }
...
...you can do this:
```javascript
var actions = Reflux.createActions(["fireBall","magicMissile"]);
var Store = Reflux.createStore({
init: function() {
this.listenToMany(actions);
},
onFireBall: function(){
// whoooosh!
},
onMagicMissile: function(){
// bzzzzapp!
}
...
function nextTick(nextTick) { _.nextTick = nextTick; }
...
Whenever action functors are called, they return immediately through the use of `setTimeout` (`nextTick` function) internally.
You may switch out for your favorite `setTimeout`, `nextTick`, `setImmediate`, et al implementation:
```javascript
// node.js env
Reflux.nextTick(process.nextTick);
```
For better alternative to `setTimeout`, you may opt to use the [`setImmediate` polyfill](https://github.com/YuzuJS/setImmediate), [`
setImmediate2`](https://github.com/Katochimoto/setImmediate) or [`macrotask`](https://github.com/calvinmetcalf/macrotask).
### Joining parallel listeners with composed listenables
...
function setEventEmitter(ctx) { _.EventEmitter = ctx; }
...
### Switching EventEmitter
Don't like to use the EventEmitter provided? You can switch to another one, such as NodeJS's own like this:
```javascript
// Do this before creating actions or stores
Reflux.setEventEmitter(require('events').EventEmitter);
```
### Switching nextTick
Whenever action functors are called, they return immediately through the use of `setTimeout` (`nextTick` function) internally.
You may switch out for your favorite `setTimeout`, `nextTick`, `setImmediate`, et al implementation:
...
setGlobalState = function (obj) { for (var storeID in obj) { if (Reflux.stores[storeID]) { Reflux.stores[storeID].setState(obj[storeID]); } else { Reflux.GlobalState[storeID] = obj[storeID]; } } }
...
// at this point it would render with a count of 50!
```
One of the most useful ways you could do this is to store a `Reflux.GlobalState` state as a JSON string in order to implement it
again the next time the app starts up and have the user begin right where they left off.
#### Reflux.setGlobalState and Reflux.getGlobalState
Directly accessing `Reflux.GlobalState` is a fine way to do set the starting state of an app and to do automated testing, but it
is also helpful to be able to manipulate the global state while the app is running as well. To do this Reflux exposes a `Reflux
.getGlobalState()` function and a `Reflux.setGlobalState()` function. The former allows
you to get a deep copy of the current global state (so that the copy will not mutate as the global state itself continues to mutate
) and the latter allows you to set part or all of the global state at any time in the program. Between these two functions things
like state time-travel, undo/redo, and move-by-move tracking become relatively easy.
### Making sure Reflux.Component is available
`Reflux.Component` extends `React.Component`. Therefore Reflux needs to be able to access React in order to expose it. In the browser
as long as React is loaded before Reflux then Reflux will automatically find it. Likewise in node-like environments where `require
('react')` will function Reflux will try to access React that way. So in almost all situations Reflux will find React
on its own.
However, Reflux also exposes the method `Reflux.defineReact` that you can use to manually give Reflux a reference to the React object
in case you need to:
...
function use(pluginCb) { pluginCb(this); }
n/a
function EventEmitter() { /* Nothing to set */ }
n/a
Component = function (props, context, updater) { _extend.call(this, props, context, updater); }
n/a
extend = function (clss) { return defineReact(null, null, clss); }
...
### Extending a 3rd Party Class
Sometimes 3rd party libraries will have their own class that extends `React.Component` that they require you to use. Reflux handles
this by exposing the `Reflux.Component.extend` method. If you have such a 3rd party class you can pass that class to this method
and it will return a version of `Reflux.Component` that extends it instead of extending `React.Component` directly. Example:
```javascript
import {ThirdPartyComponent} from 'third-party';
var RefluxThirdPartyComponent = Reflux.Component.extend(ThirdPartyComponent);
class MyComponent extends RefluxThirdPartyComponent
{
// ...
}
```
...
componentWillMount = function () { // if there is a this.store then simply push it onto the this.stores array or make one if needed if (this.store) { if (Array.isArray(this.stores)) { this.stores.unshift(this.store); } else { this.stores = [this.store]; } } if (this.stores) { this.__storeunsubscribes__ = this.__storeunsubscribes__ || []; var sS = this.setState.bind(this); // this handles the triggering of a store, checking what's updated if proto.storeKeys is utilized var onStoreTrigger = function(obj){ var updateObj = filterByStoreKeys(this.storeKeys, obj); if (updateObj) { sS(updateObj); } }.bind(this); // for each store in this.stores... for (var i = 0, ii = this.stores.length; i < ii; i++) { var str = this.stores[i]; // if's a function then we know it's a class getting passed, not an instance if (typeof str === 'function') { var storeId = str.id; // if there is NOT a .singleton property on the store then this store has not been initialized yet, so do so if (!str.singleton) { str.singleton = new str(); if (storeId) { Reflux.stores[storeId] = str.singleton; } } // before we weren't sure if we were working with an instance or class, so now we know an instance is created set it // to the variables we were using so that we can just continue on knowing it's the instance we're working with this.stores[i] = str = str.singleton; // the instance should have an .id property as well if the class does, so set that here str.id = storeId; // if there is an id and there is a global state property for this store then merge // the properties from that global state into the default state of the store AND then // set the global state to that new state (since it may have previously been partial) if (storeId && Reflux.GlobalState[storeId]) { for (var key in Reflux.GlobalState[storeId]) { str.state[key] = Reflux.GlobalState[storeId][key]; } Reflux.GlobalState[storeId] = str.state; // otherwise (if it has an id) set the global state to the default state of the store } else if (storeId) { Reflux.GlobalState[storeId] = str.state; } // if no id, then no messing with global state } // listen/subscribe for the ".trigger()" in the store, and track the unsubscribes so that we can unsubscribe on unmount if (!Reflux.serverMode) { this.__storeunsubscribes__.push(str.listen(onStoreTrigger)); } // run set state so that it mixes in the props from the store with the component var updateObj = filterByStoreKeys(this.storeKeys, str.state); if (updateObj) { this.setState(updateObj); } } } // mapStoreToState needs to know if is ready to map or must wait this.__readytomap__ = true; // if there are mappings that were delayed, do them now var dmaps = this.__delayedmaps__; if (dmaps) { for (var j=0,jj=dmaps.length; j<jj; j++) { dmaps[j].func( dmaps[j].state ); } } this.__delayedmaps__ = null; }
...
```javascript
// ...
componentWillMount()
{
// ... your stuff ...
super.componentWillMount();
}
//...
```
### More:
...
componentWillUnmount = function () { if (this.__storeunsubscribes__) { for (var i = 0, ii = this.__storeunsubscribes__.length; i < ii; i++) { this.__storeunsubscribes__[i](); } } this.__readytomap__ = false; }
n/a
mapStoreToState = function (store, filterFunc) { // make sure we have a proper singleton instance to work with if (typeof store === 'function') { if (store.singleton) { store = store.singleton; } else { store = Reflux.initStore(store); } } // we need a closure so that the called function can remember the proper filter function to use, so function gets defined here var self = this; function onMapStoreTrigger(obj) { // get an object var update = filterFunc.call(self, obj); // if no object returned from filter functions do nothing if (!update) { return; } // check if the update actually has any mapped props /*jshint unused: false */ var hasProps = false; for (var check in update) { hasProps = true; break; } // if there were props mapped, then update via setState if (hasProps) { self.setState(update); } } // add the listener to know when the store is triggered this.__storeunsubscribes__ = this.__storeunsubscribes__ || []; this.__storeunsubscribes__.push(store.listen(onMapStoreTrigger)); // now actually run onMapStoreTrigger with the full store state so that we immediately have all store state mapped to component state if (this.__readytomap__) { onMapStoreTrigger(store.state); } else { this.__delayedmaps__ = this.__delayedmaps__ || []; this.__delayedmaps__.push({func:onMapStoreTrigger, state:store.state}); } }
...
This method takes 2 arguments: the `Reflux.Store` you want mapped to the component state (either the class itself or the singleton
instance) and a mapping function supplied by you. The mapping function will be called any time the store instance's `setState
` is used to change the state of the store. The mapping function takes an argument which will be the state change object from the
store for that particular change. It needs to return an object which will then be mapped to the component state (similar to if
that very returned object were used in the component's `setState`). If an object with no properties is returned then the component
will *not* re-render. The mapping function is also called with its `this` keyword representing the component, so comparing store
values to current component state values via `this.state` is possible as well.
```javascript
class Counter extends Reflux.Component
{
constructor(props) {
super(props);
this.mapStoreToState(MyStoreClass, function(fromStore){
var obj = {};
if (fromStore.color)
obj.color = fromStore.color;
if (fromStore.data && fromStore.data.classToUse)
obj.class = fromStore.data.classToUse;
return obj;
});
...
function fetchInitialState(listenable, defaultCallback) { defaultCallback = defaultCallback && this[defaultCallback] || defaultCallback; var me = this; if (_.isFunction(defaultCallback) && _.isFunction(listenable.getInitialState)) { var data = listenable.getInitialState(); if (data && _.isFunction(data.then)) { data.then(function () { defaultCallback.apply(me, arguments); }); } else { defaultCallback.call(this, data); } } }
...
*/
var listenTo = exports.listenTo = function listenTo(listenable, callback, defaultCallback) {
var desub,
unsubscriber,
subscriptionobj,
subs = this.subscriptions = this.subscriptions || [];
_.throwIf(this.validateListening(listenable));
this.fetchInitialState(listenable, defaultCallback);
desub = listenable.listen(this[callback] || callback, this);
unsubscriber = function unsubscriber() {
var index = subs.indexOf(subscriptionobj);
_.throwIf(index === -1, "Tried to remove listen already gone from subscriptions list!");
subs.splice(index, 1);
desub();
};
...
function hasListener(listenable) { var i = 0, j, listener, listenables; for (; i < (this.subscriptions || []).length; ++i) { listenables = [].concat(this.subscriptions[i].listenable); for (j = 0; j < listenables.length; j++) { listener = listenables[j]; if (listener === listenable || listener.hasListener && listener.hasListener(listenable)) { return true; } } } return false; }
...
j,
listener,
listenables;
for (; i < (this.subscriptions || []).length; ++i) {
listenables = [].concat(this.subscriptions[i].listenable);
for (j = 0; j < listenables.length; j++) {
listener = listenables[j];
if (listener === listenable || listener.hasListener && listener.hasListener
(listenable)) {
return true;
}
}
}
return false;
};
...
joinConcat = function () /* listenables..., callback*/{ _.throwIf(arguments.length < 2, "Cannot create a join with less than 2 listenables!"); var listenables = slice.call(arguments), callback = listenables.pop(), numberOfListenables = listenables.length, join = { numberOfListenables: numberOfListenables, callback: this[callback] || callback, listener: this, strategy: strategy }, i, cancels = [], subobj; for (i = 0; i < numberOfListenables; i++) { _.throwIf(this.validateListening(listenables[i])); } for (i = 0; i < numberOfListenables; i++) { cancels.push(listenables[i].listen(newListener(i, join), this)); } reset(join); subobj = { listenable: listenables }; subobj.stop = makeStopper(subobj, cancels, this); this.subscriptions = (this.subscriptions || []).concat(subobj); return subobj; }
n/a
joinLeading = function () /* listenables..., callback*/{ _.throwIf(arguments.length < 2, "Cannot create a join with less than 2 listenables!"); var listenables = slice.call(arguments), callback = listenables.pop(), numberOfListenables = listenables.length, join = { numberOfListenables: numberOfListenables, callback: this[callback] || callback, listener: this, strategy: strategy }, i, cancels = [], subobj; for (i = 0; i < numberOfListenables; i++) { _.throwIf(this.validateListening(listenables[i])); } for (i = 0; i < numberOfListenables; i++) { cancels.push(listenables[i].listen(newListener(i, join), this)); } reset(join); subobj = { listenable: listenables }; subobj.stop = makeStopper(subobj, cancels, this); this.subscriptions = (this.subscriptions || []).concat(subobj); return subobj; }
n/a
joinStrict = function () /* listenables..., callback*/{ _.throwIf(arguments.length < 2, "Cannot create a join with less than 2 listenables!"); var listenables = slice.call(arguments), callback = listenables.pop(), numberOfListenables = listenables.length, join = { numberOfListenables: numberOfListenables, callback: this[callback] || callback, listener: this, strategy: strategy }, i, cancels = [], subobj; for (i = 0; i < numberOfListenables; i++) { _.throwIf(this.validateListening(listenables[i])); } for (i = 0; i < numberOfListenables; i++) { cancels.push(listenables[i].listen(newListener(i, join), this)); } reset(join); subobj = { listenable: listenables }; subobj.stop = makeStopper(subobj, cancels, this); this.subscriptions = (this.subscriptions || []).concat(subobj); return subobj; }
n/a
joinTrailing = function () /* listenables..., callback*/{ _.throwIf(arguments.length < 2, "Cannot create a join with less than 2 listenables!"); var listenables = slice.call(arguments), callback = listenables.pop(), numberOfListenables = listenables.length, join = { numberOfListenables: numberOfListenables, callback: this[callback] || callback, listener: this, strategy: strategy }, i, cancels = [], subobj; for (i = 0; i < numberOfListenables; i++) { _.throwIf(this.validateListening(listenables[i])); } for (i = 0; i < numberOfListenables; i++) { cancels.push(listenables[i].listen(newListener(i, join), this)); } reset(join); subobj = { listenable: listenables }; subobj.stop = makeStopper(subobj, cancels, this); this.subscriptions = (this.subscriptions || []).concat(subobj); return subobj; }
...
#### Using the listener instance methods
All objects using the listener API (stores, React components using `ListenerMixin`, or other components using the `ListenerMethods
`) gain access to the four join instance methods, named after the argument strategy. Here's an example saving the last emission
from each publisher:
```javascript
var gainHeroBadgeStore = Reflux.createStore({
init: function() {
this.joinTrailing(actions.disarmBomb, actions.saveHostage, actions.recoverData
, this.triggerAsync);
}
});
actions.disarmBomb("warehouse");
actions.recoverData("seedyletter");
actions.disarmBomb("docks");
actions.saveHostage("offices",3);
...
function listenTo(listenable, callback, defaultCallback) { var desub, unsubscriber, subscriptionobj, subs = this.subscriptions = this.subscriptions || []; _.throwIf(this.validateListening(listenable)); this.fetchInitialState(listenable, defaultCallback); desub = listenable.listen(this[callback] || callback, this); unsubscriber = function unsubscriber() { var index = subs.indexOf(subscriptionobj); _.throwIf(index === -1, "Tried to remove listen already gone from subscriptions list!"); subs.splice(index, 1); desub(); }; subscriptionobj = { stop: unsubscriber, listenable: listenable }; subs.push(subscriptionobj); return subscriptionobj; }
...
// Creates a DataStore
var statusStore = Reflux.createStore({
// Initial setup
init: function() {
// Register statusUpdate action
this.listenTo(statusUpdate, this.output);
},
// Callback
output: function(flag) {
var status = flag ? 'ONLINE' : 'OFFLINE';
// Pass on to listeners
...
function listenToMany(listenables) { var allListenables = flattenListenables(listenables); for (var key in allListenables) { var cbname = _.callbackName(key), localname = this[cbname] ? cbname : this[key] ? key : undefined; if (localname) { this.listenTo(allListenables[key], localname, this[cbname + "Default"] || this[localname + "Default"] || localname); } } }
...
...you can do this:
```javascript
var actions = Reflux.createActions(["fireBall","magicMissile"]);
var Store = Reflux.createStore({
init: function() {
this.listenToMany(actions);
},
onFireBall: function(){
// whoooosh!
},
onMagicMissile: function(){
// bzzzzapp!
}
...
function stopListeningTo(listenable) { var sub, i = 0, subs = this.subscriptions || []; for (; i < subs.length; i++) { sub = subs[i]; if (sub.listenable === listenable) { sub.stop(); _.throwIf(subs.indexOf(sub) !== -1, "Failed to remove listen from subscriptions list!"); return true; } } return false; }
n/a
function stopListeningToAll() { var remaining, subs = this.subscriptions || []; while (remaining = subs.length) { subs[0].stop(); _.throwIf(subs.length !== remaining - 1, "Failed to remove listen from subscriptions list!"); } }
n/a
function validateListening(listenable) { if (listenable === this) { return "Listener is not able to listen to itself"; } if (!_.isFunction(listenable.listen)) { return listenable + " is missing a listen method"; } if (listenable.hasListener && listenable.hasListener(this)) { return "Listener cannot listen to this listenable because of circular loop"; } }
...
* @returns {Object} A subscription obj where `stop` is an unsub function and `listenable` is the object being listened to
*/
var listenTo = exports.listenTo = function listenTo(listenable, callback, defaultCallback) {
var desub,
unsubscriber,
subscriptionobj,
subs = this.subscriptions = this.subscriptions || [];
_.throwIf(this.validateListening(listenable));
this.fetchInitialState(listenable, defaultCallback);
desub = listenable.listen(this[callback] || callback, this);
unsubscriber = function unsubscriber() {
var index = subs.indexOf(subscriptionobj);
_.throwIf(index === -1, "Tried to remove listen already gone from subscriptions list!");
subs.splice(index, 1);
desub();
...
function stopListeningToAll() { var remaining, subs = this.subscriptions || []; while (remaining = subs.length) { subs[0].stop(); _.throwIf(subs.length !== remaining - 1, "Failed to remove listen from subscriptions list!"); } }
n/a
function fetchInitialState(listenable, defaultCallback) { defaultCallback = defaultCallback && this[defaultCallback] || defaultCallback; var me = this; if (_.isFunction(defaultCallback) && _.isFunction(listenable.getInitialState)) { var data = listenable.getInitialState(); if (data && _.isFunction(data.then)) { data.then(function () { defaultCallback.apply(me, arguments); }); } else { defaultCallback.call(this, data); } } }
...
*/
var listenTo = exports.listenTo = function listenTo(listenable, callback, defaultCallback) {
var desub,
unsubscriber,
subscriptionobj,
subs = this.subscriptions = this.subscriptions || [];
_.throwIf(this.validateListening(listenable));
this.fetchInitialState(listenable, defaultCallback);
desub = listenable.listen(this[callback] || callback, this);
unsubscriber = function unsubscriber() {
var index = subs.indexOf(subscriptionobj);
_.throwIf(index === -1, "Tried to remove listen already gone from subscriptions list!");
subs.splice(index, 1);
desub();
};
...
function hasListener(listenable) { var i = 0, j, listener, listenables; for (; i < (this.subscriptions || []).length; ++i) { listenables = [].concat(this.subscriptions[i].listenable); for (j = 0; j < listenables.length; j++) { listener = listenables[j]; if (listener === listenable || listener.hasListener && listener.hasListener(listenable)) { return true; } } } return false; }
...
j,
listener,
listenables;
for (; i < (this.subscriptions || []).length; ++i) {
listenables = [].concat(this.subscriptions[i].listenable);
for (j = 0; j < listenables.length; j++) {
listener = listenables[j];
if (listener === listenable || listener.hasListener && listener.hasListener
(listenable)) {
return true;
}
}
}
return false;
};
...
joinConcat = function () /* listenables..., callback*/{ _.throwIf(arguments.length < 2, "Cannot create a join with less than 2 listenables!"); var listenables = slice.call(arguments), callback = listenables.pop(), numberOfListenables = listenables.length, join = { numberOfListenables: numberOfListenables, callback: this[callback] || callback, listener: this, strategy: strategy }, i, cancels = [], subobj; for (i = 0; i < numberOfListenables; i++) { _.throwIf(this.validateListening(listenables[i])); } for (i = 0; i < numberOfListenables; i++) { cancels.push(listenables[i].listen(newListener(i, join), this)); } reset(join); subobj = { listenable: listenables }; subobj.stop = makeStopper(subobj, cancels, this); this.subscriptions = (this.subscriptions || []).concat(subobj); return subobj; }
n/a
joinLeading = function () /* listenables..., callback*/{ _.throwIf(arguments.length < 2, "Cannot create a join with less than 2 listenables!"); var listenables = slice.call(arguments), callback = listenables.pop(), numberOfListenables = listenables.length, join = { numberOfListenables: numberOfListenables, callback: this[callback] || callback, listener: this, strategy: strategy }, i, cancels = [], subobj; for (i = 0; i < numberOfListenables; i++) { _.throwIf(this.validateListening(listenables[i])); } for (i = 0; i < numberOfListenables; i++) { cancels.push(listenables[i].listen(newListener(i, join), this)); } reset(join); subobj = { listenable: listenables }; subobj.stop = makeStopper(subobj, cancels, this); this.subscriptions = (this.subscriptions || []).concat(subobj); return subobj; }
n/a
joinStrict = function () /* listenables..., callback*/{ _.throwIf(arguments.length < 2, "Cannot create a join with less than 2 listenables!"); var listenables = slice.call(arguments), callback = listenables.pop(), numberOfListenables = listenables.length, join = { numberOfListenables: numberOfListenables, callback: this[callback] || callback, listener: this, strategy: strategy }, i, cancels = [], subobj; for (i = 0; i < numberOfListenables; i++) { _.throwIf(this.validateListening(listenables[i])); } for (i = 0; i < numberOfListenables; i++) { cancels.push(listenables[i].listen(newListener(i, join), this)); } reset(join); subobj = { listenable: listenables }; subobj.stop = makeStopper(subobj, cancels, this); this.subscriptions = (this.subscriptions || []).concat(subobj); return subobj; }
n/a
joinTrailing = function () /* listenables..., callback*/{ _.throwIf(arguments.length < 2, "Cannot create a join with less than 2 listenables!"); var listenables = slice.call(arguments), callback = listenables.pop(), numberOfListenables = listenables.length, join = { numberOfListenables: numberOfListenables, callback: this[callback] || callback, listener: this, strategy: strategy }, i, cancels = [], subobj; for (i = 0; i < numberOfListenables; i++) { _.throwIf(this.validateListening(listenables[i])); } for (i = 0; i < numberOfListenables; i++) { cancels.push(listenables[i].listen(newListener(i, join), this)); } reset(join); subobj = { listenable: listenables }; subobj.stop = makeStopper(subobj, cancels, this); this.subscriptions = (this.subscriptions || []).concat(subobj); return subobj; }
...
#### Using the listener instance methods
All objects using the listener API (stores, React components using `ListenerMixin`, or other components using the `ListenerMethods
`) gain access to the four join instance methods, named after the argument strategy. Here's an example saving the last emission
from each publisher:
```javascript
var gainHeroBadgeStore = Reflux.createStore({
init: function() {
this.joinTrailing(actions.disarmBomb, actions.saveHostage, actions.recoverData
, this.triggerAsync);
}
});
actions.disarmBomb("warehouse");
actions.recoverData("seedyletter");
actions.disarmBomb("docks");
actions.saveHostage("offices",3);
...
function listenTo(listenable, callback, defaultCallback) { var desub, unsubscriber, subscriptionobj, subs = this.subscriptions = this.subscriptions || []; _.throwIf(this.validateListening(listenable)); this.fetchInitialState(listenable, defaultCallback); desub = listenable.listen(this[callback] || callback, this); unsubscriber = function unsubscriber() { var index = subs.indexOf(subscriptionobj); _.throwIf(index === -1, "Tried to remove listen already gone from subscriptions list!"); subs.splice(index, 1); desub(); }; subscriptionobj = { stop: unsubscriber, listenable: listenable }; subs.push(subscriptionobj); return subscriptionobj; }
...
// Creates a DataStore
var statusStore = Reflux.createStore({
// Initial setup
init: function() {
// Register statusUpdate action
this.listenTo(statusUpdate, this.output);
},
// Callback
output: function(flag) {
var status = flag ? 'ONLINE' : 'OFFLINE';
// Pass on to listeners
...
function listenToMany(listenables) { var allListenables = flattenListenables(listenables); for (var key in allListenables) { var cbname = _.callbackName(key), localname = this[cbname] ? cbname : this[key] ? key : undefined; if (localname) { this.listenTo(allListenables[key], localname, this[cbname + "Default"] || this[localname + "Default"] || localname); } } }
...
...you can do this:
```javascript
var actions = Reflux.createActions(["fireBall","magicMissile"]);
var Store = Reflux.createStore({
init: function() {
this.listenToMany(actions);
},
onFireBall: function(){
// whoooosh!
},
onMagicMissile: function(){
// bzzzzapp!
}
...
function stopListeningTo(listenable) { var sub, i = 0, subs = this.subscriptions || []; for (; i < subs.length; i++) { sub = subs[i]; if (sub.listenable === listenable) { sub.stop(); _.throwIf(subs.indexOf(sub) !== -1, "Failed to remove listen from subscriptions list!"); return true; } } return false; }
n/a
function stopListeningToAll() { var remaining, subs = this.subscriptions || []; while (remaining = subs.length) { subs[0].stop(); _.throwIf(subs.length !== remaining - 1, "Failed to remove listen from subscriptions list!"); } }
n/a
function validateListening(listenable) { if (listenable === this) { return "Listener is not able to listen to itself"; } if (!_.isFunction(listenable.listen)) { return listenable + " is missing a listen method"; } if (listenable.hasListener && listenable.hasListener(this)) { return "Listener cannot listen to this listenable because of circular loop"; } }
...
* @returns {Object} A subscription obj where `stop` is an unsub function and `listenable` is the object being listened to
*/
var listenTo = exports.listenTo = function listenTo(listenable, callback, defaultCallback) {
var desub,
unsubscriber,
subscriptionobj,
subs = this.subscriptions = this.subscriptions || [];
_.throwIf(this.validateListening(listenable));
this.fetchInitialState(listenable, defaultCallback);
desub = listenable.listen(this[callback] || callback, this);
unsubscriber = function unsubscriber() {
var index = subs.indexOf(subscriptionobj);
_.throwIf(index === -1, "Tried to remove listen already gone from subscriptions list!");
subs.splice(index, 1);
desub();
...
function deferWith(callback) { var oldTrigger = this.trigger, ctx = this, resolver = function resolver() { oldTrigger.apply(ctx, arguments); }; this.trigger = function () { callback.apply(ctx, [resolver].concat([].splice.call(arguments, 0))); }; }
n/a
function listen(callback, bindContext) { bindContext = bindContext || this; var eventHandler = function eventHandler(args) { if (aborted) { return; } callback.apply(bindContext, args); }, me = this, aborted = false; this.emitter.addListener(this.eventLabel, eventHandler); return function () { aborted = true; me.emitter.removeListener(me.eventLabel, eventHandler); }; }
...
```javascript
// this creates 'load', 'load.completed' and 'load.failed'
var Actions = Reflux.createActions({
"load": {children: ["completed","failed"]}
});
// when 'load' is triggered, call async operation and trigger related actions
Actions.load.listen( function() {
// By default, the listener is bound to the action
// so we can access child actions using 'this'
someAsyncOperation()
.then( this.completed )
.catch( this.failed );
});
```
...
function preEmit() {}
n/a
function shouldEmit() { return true; }
n/a
function trigger() { var args = arguments, pre = this.preEmit.apply(this, args); args = pre === undefined ? args : _.isArguments(pre) ? pre : [].concat(pre); if (this.shouldEmit.apply(this, args)) { this.emitter.emit(this.eventLabel, args); } }
...
},
// Callback
output: function(flag) {
var status = flag ? 'ONLINE' : 'OFFLINE';
// Pass on to listeners
this.trigger(status);
}
});
```
In the above example, whenever the action is called, the store's `output` callback will be called with whatever parameters
were sent in the action. E.g. if the action is called as `statusUpdate(true)` then the flag argument in `output` function is `true
`.
...
function triggerAsync() { var args = arguments, me = this; _.nextTick(function () { me.trigger.apply(me, args); }); }
...
var statusUpdate = Reflux.createAction(options);
```
An action is a [function object](http://en.wikipedia.org/wiki/Function_object) that can be invoked like any function.
```javascript
statusUpdate(data); // Invokes the action statusUpdate
statusUpdate.triggerAsync(data); // same effect as above
```
If `options.sync` is true, the functor will instead call `action.trigger` which is a synchronous operation. You can change `action
.sync` during the lifetime of the action, and the following calls will honour that change.
There is also a convenience function for creating multiple actions.
```javascript
...
PureComponent = function (props, context, updater) { _extend.call(this, props, context, updater); }
n/a
extend = function (clss) { return defineReact(null, null, clss); }
...
### Extending a 3rd Party Class
Sometimes 3rd party libraries will have their own class that extends `React.Component` that they require you to use. Reflux handles
this by exposing the `Reflux.Component.extend` method. If you have such a 3rd party class you can pass that class to this method
and it will return a version of `Reflux.Component` that extends it instead of extending `React.Component` directly. Example:
```javascript
import {ThirdPartyComponent} from 'third-party';
var RefluxThirdPartyComponent = Reflux.Component.extend(ThirdPartyComponent);
class MyComponent extends RefluxThirdPartyComponent
{
// ...
}
```
...
componentWillMount = function () { // if there is a this.store then simply push it onto the this.stores array or make one if needed if (this.store) { if (Array.isArray(this.stores)) { this.stores.unshift(this.store); } else { this.stores = [this.store]; } } if (this.stores) { this.__storeunsubscribes__ = this.__storeunsubscribes__ || []; var sS = this.setState.bind(this); // this handles the triggering of a store, checking what's updated if proto.storeKeys is utilized var onStoreTrigger = function(obj){ var updateObj = filterByStoreKeys(this.storeKeys, obj); if (updateObj) { sS(updateObj); } }.bind(this); // for each store in this.stores... for (var i = 0, ii = this.stores.length; i < ii; i++) { var str = this.stores[i]; // if's a function then we know it's a class getting passed, not an instance if (typeof str === 'function') { var storeId = str.id; // if there is NOT a .singleton property on the store then this store has not been initialized yet, so do so if (!str.singleton) { str.singleton = new str(); if (storeId) { Reflux.stores[storeId] = str.singleton; } } // before we weren't sure if we were working with an instance or class, so now we know an instance is created set it // to the variables we were using so that we can just continue on knowing it's the instance we're working with this.stores[i] = str = str.singleton; // the instance should have an .id property as well if the class does, so set that here str.id = storeId; // if there is an id and there is a global state property for this store then merge // the properties from that global state into the default state of the store AND then // set the global state to that new state (since it may have previously been partial) if (storeId && Reflux.GlobalState[storeId]) { for (var key in Reflux.GlobalState[storeId]) { str.state[key] = Reflux.GlobalState[storeId][key]; } Reflux.GlobalState[storeId] = str.state; // otherwise (if it has an id) set the global state to the default state of the store } else if (storeId) { Reflux.GlobalState[storeId] = str.state; } // if no id, then no messing with global state } // listen/subscribe for the ".trigger()" in the store, and track the unsubscribes so that we can unsubscribe on unmount if (!Reflux.serverMode) { this.__storeunsubscribes__.push(str.listen(onStoreTrigger)); } // run set state so that it mixes in the props from the store with the component var updateObj = filterByStoreKeys(this.storeKeys, str.state); if (updateObj) { this.setState(updateObj); } } } // mapStoreToState needs to know if is ready to map or must wait this.__readytomap__ = true; // if there are mappings that were delayed, do them now var dmaps = this.__delayedmaps__; if (dmaps) { for (var j=0,jj=dmaps.length; j<jj; j++) { dmaps[j].func( dmaps[j].state ); } } this.__delayedmaps__ = null; }
...
```javascript
// ...
componentWillMount()
{
// ... your stuff ...
super.componentWillMount();
}
//...
```
### More:
...
componentWillUnmount = function () { if (this.__storeunsubscribes__) { for (var i = 0, ii = this.__storeunsubscribes__.length; i < ii; i++) { this.__storeunsubscribes__[i](); } } this.__readytomap__ = false; }
n/a
mapStoreToState = function (store, filterFunc) { // make sure we have a proper singleton instance to work with if (typeof store === 'function') { if (store.singleton) { store = store.singleton; } else { store = Reflux.initStore(store); } } // we need a closure so that the called function can remember the proper filter function to use, so function gets defined here var self = this; function onMapStoreTrigger(obj) { // get an object var update = filterFunc.call(self, obj); // if no object returned from filter functions do nothing if (!update) { return; } // check if the update actually has any mapped props /*jshint unused: false */ var hasProps = false; for (var check in update) { hasProps = true; break; } // if there were props mapped, then update via setState if (hasProps) { self.setState(update); } } // add the listener to know when the store is triggered this.__storeunsubscribes__ = this.__storeunsubscribes__ || []; this.__storeunsubscribes__.push(store.listen(onMapStoreTrigger)); // now actually run onMapStoreTrigger with the full store state so that we immediately have all store state mapped to component state if (this.__readytomap__) { onMapStoreTrigger(store.state); } else { this.__delayedmaps__ = this.__delayedmaps__ || []; this.__delayedmaps__.push({func:onMapStoreTrigger, state:store.state}); } }
...
This method takes 2 arguments: the `Reflux.Store` you want mapped to the component state (either the class itself or the singleton
instance) and a mapping function supplied by you. The mapping function will be called any time the store instance's `setState
` is used to change the state of the store. The mapping function takes an argument which will be the state change object from the
store for that particular change. It needs to return an object which will then be mapped to the component state (similar to if
that very returned object were used in the component's `setState`). If an object with no properties is returned then the component
will *not* re-render. The mapping function is also called with its `this` keyword representing the component, so comparing store
values to current component state values via `this.state` is possible as well.
```javascript
class Counter extends Reflux.Component
{
constructor(props) {
super(props);
this.mapStoreToState(MyStoreClass, function(fromStore){
var obj = {};
if (fromStore.color)
obj.color = fromStore.color;
if (fromStore.data && fromStore.data.classToUse)
obj.class = fromStore.data.classToUse;
return obj;
});
...
Store = function () { // extending doesn't really work well here, so instead we create an internal instance // and just loop through its properties/methods and make a getter/setter for each // that will actually be getting and setting on that internal instance. this.__store__ = Reflux.createStore(); this.state = {}; var self = this; for (var key in this.__store__) { /*jshint loopfunc: true */ (function (prop) { Object.defineProperty(self, prop, { get: function () { return self.__store__[prop]; }, set: function (v) { self.__store__[prop] = v; } }); })(key); } }
n/a
setState = function (obj) { // Object.assign(this.state, obj); // later turn this to Object.assign and remove loop once support is good enough for (var key in obj) { this.state[key] = obj[key]; } // if there's an id (i.e. it's being tracked by the global state) then make sure to update the global state if (this.id) { Reflux.GlobalState[this.id] = this.state; } // trigger, because any component it's attached to is listening and will merge the store state into its own on a store trigger this.trigger(obj); }
...
Register your component to listen for changes in your data stores, preferably in the `componentDidMount` [lifecycle method](http
://facebook.github.io/react/docs/component-specs.html) and unregister in the `componentWillUnmount`, like this:
```javascript
var Status = React.createClass({
getInitialState: function() { },
onStatusChange: function(status) {
this.setState({
currentStatus: status
});
},
componentDidMount: function() {
this.unsubscribe = statusStore.listen(this.onStatusChange);
},
componentWillUnmount: function() {
...
function addAction(act) { if (use) { createdActions.push(act); } }
...
var async = !functor.sync && typeof functor.sync !== "undefined" || hasChildActions;
var triggerType = async ? "triggerAsync" : "trigger";
return functor[triggerType].apply(functor, arguments);
};
_.extend(functor, childActions, context);
Keep.addAction(functor);
return functor;
}
},{"./ActionMethods":2,"./Keep":3,"./PublisherMethods":5,"./utils":13}],9:[function(require
,module,exports){
"use strict";
Object.defineProperty(exports, "__esModule", {
...
function addStore(str) { if (use) { createdStores.push(str); } }
...
}
}
}
_.extend(Store.prototype, ListenerMethods, PublisherMethods, StoreMethods, definition);
var store = new Store();
Keep.addStore(store);
return store;
}
},{"./Keep":3,"./ListenerMethods":4,"./PublisherMethods":5,"./StoreMethods":6,"./bindMethods
":7,"./mixer":12,"./utils":13}],10:[function(require,module,exports){
"use strict";
Object.defineProperty(exports, "__esModule", {
...
function reset() { while (createdStores.length) { createdStores.pop(); } while (createdActions.length) { createdActions.pop(); } }
n/a
function useKeep() { var bool = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true; use = bool; }
n/a
function EventEmitter() { /* Nothing to set */ }
...
var chDef = definition.children[i];
var chName = typeof chDef === "string" ? chDef : chDef.actionName;
childActions[chName] = createAction(chDef);
}
var context = _.extend({
eventLabel: "action",
emitter: new _.EventEmitter(),
_isAction: true
}, PublisherMethods, ActionMethods, definition);
var functor = function functor() {
var hasChildActions = false;
/* eslint no-unused-vars:0 */
for (var ignore in functor.childActions) {
...
function callbackName(string, prefix) { prefix = prefix || "on"; return prefix + exports.capitalize(string); }
...
* A convenience method that listens to all listenables in the given object.
*
* @param {Object} listenables An object of listenables. Keys will be used as callback method names.
*/
var listenToMany = exports.listenToMany = function listenToMany(listenables) {
var allListenables = flattenListenables(listenables);
for (var key in allListenables) {
var cbname = _.callbackName(key),
localname = this[cbname] ? cbname : this[key] ? key : undefined;
if (localname) {
this.listenTo(allListenables[key], localname, this[cbname + "Default"] || this[localname + "Default
x22;] || localname);
}
}
};
...
function capitalize(string) { return string.charAt(0).toUpperCase() + string.slice(1); }
...
// recursively flatten children
var children = flattenListenables(childMap);
// add the primary listenable and chilren
flattened[key] = listenable;
for (var childKey in children) {
var childListenable = children[childKey];
flattened[key + _.capitalize(childKey)] = childListenable;
}
}
return flattened;
};
/**
...
function extend(obj) { if (!isObject(obj)) { return obj; } var source, keys, prop; for (var i = 1, length = arguments.length; i < length; i++) { source = arguments[i]; keys = Object.keys(source); for (var j = 0; j < keys.length; j++) { prop = keys[j]; if (Object.getOwnPropertyDescriptor && Object.defineProperty) { var propertyDescriptor = Object.getOwnPropertyDescriptor(source, prop); Object.defineProperty(obj, prop, propertyDescriptor); } else { obj[prop] = source[prop]; } } } return obj; }
...
### Extending a 3rd Party Class
Sometimes 3rd party libraries will have their own class that extends `React.Component` that they require you to use. Reflux handles
this by exposing the `Reflux.Component.extend` method. If you have such a 3rd party class you can pass that class to this method
and it will return a version of `Reflux.Component` that extends it instead of extending `React.Component` directly. Example:
```javascript
import {ThirdPartyComponent} from 'third-party';
var RefluxThirdPartyComponent = Reflux.Component.extend(ThirdPartyComponent);
class MyComponent extends RefluxThirdPartyComponent
{
// ...
}
```
...
inherits = function (subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) { if (Object.setPrototypeOf) { Object.setPrototypeOf(subClass, superClass); } else { /* jshint proto: true */ subClass.__proto__ = superClass; } } }
...
* NOTE: New `MyStore.state` shortcut (from 6.4.0) is not available in IE10 and below, use accordingly in your projects.
* Remove test for it so that it doesn't fail, and is undocumented feature for now, until the day we can drop IE9 and IE10.
## v6.4.0
* Improved on class extending function used internally.
* Made that extending function available externally at `Reflux.utils.inherits(NewClass
, InheritsFrom)` so that it can be used for testing.
* Made `MyStore.state` work as shortcut access to `MyStore.singleton.state` (not available in IE9 and IE10, plan usage accordingly
for your project).
## v6.3.0
* Added Reflux.PureComponent which extends from React.PureComponent instead of React.Component.
## v6.2.0
...
function isArguments(value) { return (typeof value === "undefined" ? "undefined" : _typeof(value)) === "object" && "callee" in value && typeof value.length === "number"; }
...
/**
* Publishes an event using `this.emitter` (if `shouldEmit` agrees)
*/
var trigger = exports.trigger = function trigger() {
var args = arguments,
pre = this.preEmit.apply(this, args);
args = pre === undefined ? args : _.isArguments(pre) ? pre : [].concat(pre);
if (this.shouldEmit.apply(this, args)) {
this.emitter.emit(this.eventLabel, args);
}
};
/**
* Tries to publish the event on the next tick
...
function isFunction(value) { return typeof value === "function"; }
...
* listened to.
* @returns {String|Undefined} An error message, or undefined if there was no problem.
*/
var validateListening = exports.validateListening = function validateListening(listenable) {
if (listenable === this) {
return "Listener is not able to listen to itself";
}
if (!_.isFunction(listenable.listen)) {
return listenable + " is missing a listen method";
}
if (listenable.hasListener && listenable.hasListener(this)) {
return "Listener cannot listen to this listenable because of circular loop";
}
};
...
function isObject(obj) { var type = typeof obj === "undefined" ? "undefined" : _typeof(obj); return type === "function" || type === "object" && !!obj; }
...
* be overridden in the definition object.
*
* @param {Object} definition The action object definition
*/
function createAction(definition) {
definition = definition || {};
if (!_.isObject(definition)) {
definition = { actionName: definition };
}
for (var a in ActionMethods) {
if (!allowed[a] && PublisherMethods[a]) {
throw new Error("Cannot override API method " + a + " in Reflux.ActionMethods. Use another method name or
override it on Reflux.PublisherMethods instead.");
}
...
function nextTick(callback) { setTimeout(callback, 0); }
...
Whenever action functors are called, they return immediately through the use of `setTimeout` (`nextTick` function) internally.
You may switch out for your favorite `setTimeout`, `nextTick`, `setImmediate`, et al implementation:
```javascript
// node.js env
Reflux.nextTick(process.nextTick);
```
For better alternative to `setTimeout`, you may opt to use the [`setImmediate` polyfill](https://github.com/YuzuJS/setImmediate), [`
setImmediate2`](https://github.com/Katochimoto/setImmediate) or [`macrotask`](https://github.com/calvinmetcalf/macrotask).
### Joining parallel listeners with composed listenables
...
function object(keys, vals) { var o = {}, i = 0; for (; i < keys.length; i++) { o[keys[i]] = vals[i]; } return o; }
...
return {
getInitialState: function() {
if (!_.isFunction(listenable.getInitialState)) {
return {};
}
return _.object([key],[listenable.getInitialState()]);
},
componentDidMount: function() {
var me = this;
_.extend(me, ListenerMethods);
this.listenTo(listenable, function(v) {
...
function throwIf(val, msg) { if (val) { throw Error(msg || val); } }
...
* @returns {Object} A subscription obj where `stop` is an unsub function and `listenable` is the object being listened to
*/
var listenTo = exports.listenTo = function listenTo(listenable, callback, defaultCallback) {
var desub,
unsubscriber,
subscriptionobj,
subs = this.subscriptions = this.subscriptions || [];
_.throwIf(this.validateListening(listenable));
this.fetchInitialState(listenable, defaultCallback);
desub = listenable.listen(this[callback] || callback, this);
unsubscriber = function unsubscriber() {
var index = subs.indexOf(subscriptionobj);
_.throwIf(index === -1, "Tried to remove listen already gone from subscriptions list!");
subs.splice(index, 1);
desub();
...
function EventEmitter() { /* Nothing to set */ }
...
var chDef = definition.children[i];
var chName = typeof chDef === "string" ? chDef : chDef.actionName;
childActions[chName] = createAction(chDef);
}
var context = _.extend({
eventLabel: "action",
emitter: new _.EventEmitter(),
_isAction: true
}, PublisherMethods, ActionMethods, definition);
var functor = function functor() {
var hasChildActions = false;
/* eslint no-unused-vars:0 */
for (var ignore in functor.childActions) {
...
function on(event, fn, context) { var listener = new EE(fn, context || this) , evt = prefix ? prefix + event : event; if (!this._events) this._events = prefix ? {} : Object.create(null); if (!this._events[evt]) this._events[evt] = listener; else { if (!this._events[evt].fn) this._events[evt].push(listener); else this._events[evt] = [ this._events[evt], listener ]; } return this; }
...
if (aborted) {
return;
}
callback.apply(bindContext, args);
},
me = this,
aborted = false;
this.emitter.addListener(this.eventLabel, eventHandler);
return function () {
aborted = true;
me.emitter.removeListener(me.eventLabel, eventHandler);
};
};
/**
...
function emit(event, a1, a2, a3, a4, a5) { var evt = prefix ? prefix + event : event; if (!this._events || !this._events[evt]) return false; var listeners = this._events[evt] , len = arguments.length , args , i; if ('function' === typeof listeners.fn) { if (listeners.once) this.removeListener(event, listeners.fn, undefined, true); switch (len) { case 1: return listeners.fn.call(listeners.context), true; case 2: return listeners.fn.call(listeners.context, a1), true; case 3: return listeners.fn.call(listeners.context, a1, a2), true; case 4: return listeners.fn.call(listeners.context, a1, a2, a3), true; case 5: return listeners.fn.call(listeners.context, a1, a2, a3, a4), true; case 6: return listeners.fn.call(listeners.context, a1, a2, a3, a4, a5), true; } for (i = 1, args = new Array(len -1); i < len; i++) { args[i - 1] = arguments[i]; } listeners.fn.apply(listeners.context, args); } else { var length = listeners.length , j; for (i = 0; i < length; i++) { if (listeners[i].once) this.removeListener(event, listeners[i].fn, undefined, true); switch (len) { case 1: listeners[i].fn.call(listeners[i].context); break; case 2: listeners[i].fn.call(listeners[i].context, a1); break; case 3: listeners[i].fn.call(listeners[i].context, a1, a2); break; default: if (!args) for (j = 1, args = new Array(len -1); j < len; j++) { args[j - 1] = arguments[j]; } listeners[i].fn.apply(listeners[i].context, args); } } } return true; }
...
* Publishes an event using `this.emitter` (if `shouldEmit` agrees)
*/
var trigger = exports.trigger = function trigger() {
var args = arguments,
pre = this.preEmit.apply(this, args);
args = pre === undefined ? args : _.isArguments(pre) ? pre : [].concat(pre);
if (this.shouldEmit.apply(this, args)) {
this.emitter.emit(this.eventLabel, args);
}
};
/**
* Tries to publish the event on the next tick
*/
var triggerAsync = exports.triggerAsync = function triggerAsync() {
...
function eventNames() { var events = this._events , names = [] , name; if (!events) return names; for (name in events) { if (has.call(events, name)) names.push(prefix ? name.slice(1) : name); } if (Object.getOwnPropertySymbols) { return names.concat(Object.getOwnPropertySymbols(events)); } return names; }
n/a
function listeners(event, exists) { var evt = prefix ? prefix + event : event , available = this._events && this._events[evt]; if (exists) return !!available; if (!available) return []; if (available.fn) return [available.fn]; for (var i = 0, l = available.length, ee = new Array(l); i < l; i++) { ee[i] = available[i].fn; } return ee; }
n/a
function removeListener(event, fn, context, once) { var evt = prefix ? prefix + event : event; if (!this._events || !this._events[evt]) return this; var listeners = this._events[evt] , events = []; if (fn) { if (listeners.fn) { if ( listeners.fn !== fn || (once && !listeners.once) || (context && listeners.context !== context) ) { events.push(listeners); } } else { for (var i = 0, length = listeners.length; i < length; i++) { if ( listeners[i].fn !== fn || (once && !listeners[i].once) || (context && listeners[i].context !== context) ) { events.push(listeners[i]); } } } } // // Reset the array, or remove it completely if we have no more listeners. // if (events.length) { this._events[evt] = events.length === 1 ? events[0] : events; } else { delete this._events[evt]; } return this; }
n/a
function on(event, fn, context) { var listener = new EE(fn, context || this) , evt = prefix ? prefix + event : event; if (!this._events) this._events = prefix ? {} : Object.create(null); if (!this._events[evt]) this._events[evt] = listener; else { if (!this._events[evt].fn) this._events[evt].push(listener); else this._events[evt] = [ this._events[evt], listener ]; } return this; }
n/a
function once(event, fn, context) { var listener = new EE(fn, context || this, true) , evt = prefix ? prefix + event : event; if (!this._events) this._events = prefix ? {} : Object.create(null); if (!this._events[evt]) this._events[evt] = listener; else { if (!this._events[evt].fn) this._events[evt].push(listener); else this._events[evt] = [ this._events[evt], listener ]; } return this; }
n/a
function removeAllListeners(event) { if (!this._events) return this; if (event) delete this._events[prefix ? prefix + event : event]; else this._events = prefix ? {} : Object.create(null); return this; }
n/a
function removeListener(event, fn, context, once) { var evt = prefix ? prefix + event : event; if (!this._events || !this._events[evt]) return this; var listeners = this._events[evt] , events = []; if (fn) { if (listeners.fn) { if ( listeners.fn !== fn || (once && !listeners.once) || (context && listeners.context !== context) ) { events.push(listeners); } } else { for (var i = 0, length = listeners.length; i < length; i++) { if ( listeners[i].fn !== fn || (once && !listeners[i].once) || (context && listeners[i].context !== context) ) { events.push(listeners[i]); } } } } // // Reset the array, or remove it completely if we have no more listeners. // if (events.length) { this._events[evt] = events.length === 1 ? events[0] : events; } else { delete this._events[evt]; } return this; }
...
var listeners = this._events[evt]
, len = arguments.length
, args
, i;
if ('function' === typeof listeners.fn) {
if (listeners.once) this.removeListener(event, listeners.fn, undefined, true);
switch (len) {
case 1: return listeners.fn.call(listeners.context), true;
case 2: return listeners.fn.call(listeners.context, a1), true;
case 3: return listeners.fn.call(listeners.context, a1, a2), true;
case 4: return listeners.fn.call(listeners.context, a1, a2, a3), true;
case 5: return listeners.fn.call(listeners.context, a1, a2, a3, a4), true;
...
function setMaxListeners() { return this; }
n/a