Comparing version 0.15.5 to 0.15.6
# Changelog | ||
## 0.15.6 | ||
* Adding unlisten lifecycle method. [commit](91a67d4) | ||
* AltContainer now takes in store listeners for functions. [commit](7083141) | ||
* `listen` now returns the unlisten function. [commit](864d99c) | ||
## 0.15.5 | ||
@@ -7,10 +13,10 @@ | ||
* setState has been batched, it emits a change event if there were changes. [commit](a7d98d7) | ||
* Util for having atomic transactions in stores. [commit](868a9ac) | ||
* AltNativeContainer for react-native. [commit](569b4c9) | ||
* Add shouldComponentUpdate to AltContainer. [commit](edda162) | ||
* Centralized error handling inside stores. [commit](1dfdd75) | ||
* Creating single actions. [commit](63d3a72) | ||
* You can now inject actions into your child React components using AltContainer. [commit](1bd3112) | ||
* FinalStore now contains the payload as state. [commit](b8480ba) | ||
* setState has been batched, it emits a change event if there were changes. [commit](https://github.com/goatslacker/alt/commit/a7d98d7) | ||
* Util for having atomic transactions in stores. [commit](https://github.com/goatslacker/alt/commit/868a9ac) | ||
* AltNativeContainer for react-native. [commit](https://github.com/goatslacker/alt/commit/569b4c9) | ||
* Add shouldComponentUpdate to AltContainer. [commit](https://github.com/goatslacker/alt/commit/edda162) | ||
* Centralized error handling inside stores. [commit](https://github.com/goatslacker/alt/commit/1dfdd75) | ||
* Creating single actions. [commit](https://github.com/goatslacker/alt/commit/63d3a72) | ||
* You can now inject actions into your child React components using AltContainer. [commit](https://github.com/goatslacker/alt/commit/1bd3112) | ||
* FinalStore now contains the payload as state. [commit](https://github.com/goatslacker/alt/commit/b8480ba) | ||
@@ -17,0 +23,0 @@ ## 0.15.4 |
@@ -41,3 +41,3 @@ /** | ||
var React = require('react/addons') | ||
var Subscribe = require('../mixins/Subscribe') | ||
var mixinContainer = require('./mixinContainer') | ||
var assign = require('object-assign') | ||
@@ -47,127 +47,5 @@ | ||
function getStateFromStore(store, props) { | ||
return typeof store === 'function' ? store(props) : store.getState() | ||
} | ||
function getStateFromActionsProp(actions, props) { | ||
return typeof actions === 'function' ? actions(props) : actions | ||
} | ||
var AltContainer = React.createClass({ | ||
var AltContainer = React.createClass(assign({ | ||
displayName: 'AltContainer', | ||
contextTypes: { | ||
flux: React.PropTypes.object | ||
}, | ||
getInitialState: function () { | ||
if (this.props.stores && this.props.store) { | ||
throw new ReferenceError('Cannot define both store and stores') | ||
} | ||
return this.reduceState(this.props) | ||
}, | ||
componentWillReceiveProps: function (nextProps) { | ||
this.destroySubscriptions() | ||
this.setState(this.reduceState(nextProps)) | ||
this.registerStores(nextProps) | ||
}, | ||
componentDidMount: function () { | ||
this.registerStores(this.props) | ||
}, | ||
componentWillUnmount: function () { | ||
this.destroySubscriptions() | ||
}, | ||
registerStores: function (props) { | ||
Subscribe.create(this) | ||
if (props.store) { | ||
this.addSubscription(props.store) | ||
} else if (props.stores) { | ||
var stores = props.stores | ||
if (Array.isArray(stores)) { | ||
stores.forEach(function (store) { | ||
this.addSubscription(store) | ||
}, this) | ||
} else { | ||
Object.keys(stores).forEach(function (formatter) { | ||
this.addSubscription(stores[formatter]) | ||
}, this) | ||
} | ||
} | ||
}, | ||
destroySubscriptions: function () { | ||
Subscribe.destroy(this) | ||
}, | ||
getStateFromStores: function (props) { | ||
if (props.store) { | ||
return getStateFromStore(props.store, props) | ||
} else if (props.stores) { | ||
var stores = props.stores | ||
// If you pass in an array of stores the state is merged together. | ||
if (Array.isArray(stores)) { | ||
return stores.reduce(function (obj, store) { | ||
return assign(obj, getStateFromStore(store, props)) | ||
}.bind(this), {}) | ||
// if it is an object then the state is added to the key specified | ||
} else { | ||
return Object.keys(stores).reduce(function (obj, key) { | ||
obj[key] = getStateFromStore(stores[key], props) | ||
return obj | ||
}.bind(this), {}) | ||
} | ||
} else { | ||
return {} | ||
} | ||
}, | ||
getStateFromActions: function (props) { | ||
if (props.actions) { | ||
return getStateFromActionsProp(props.actions, props) | ||
} else { | ||
return {} | ||
} | ||
}, | ||
reduceState: function (props) { | ||
return assign( | ||
{}, | ||
this.getStateFromStores(props), | ||
this.getStateFromActions(props) | ||
) | ||
}, | ||
addSubscription: function (store) { | ||
if (typeof store === 'object') { | ||
Subscribe.add(this, store, this.altSetState) | ||
} | ||
}, | ||
altSetState: function () { | ||
this.setState(this.reduceState(this.props)) | ||
}, | ||
getProps: function () { | ||
var flux = this.props.flux || this.context.flux | ||
return assign( | ||
flux ? { flux: flux } : {}, | ||
this.state | ||
) | ||
}, | ||
shouldComponentUpdate: function () { | ||
return this.props.shouldComponentUpdate | ||
? this.props.shouldComponentUpdate(this.getProps()) | ||
: true | ||
}, | ||
render: function () { | ||
@@ -192,4 +70,4 @@ var children = this.props.children | ||
} | ||
}) | ||
}, mixinContainer(React))) | ||
module.exports = AltContainer |
@@ -7,3 +7,3 @@ /** | ||
var React = require('react-native') | ||
var Subscribe = require('../mixins/Subscribe') | ||
var mixinContainer = require('./mixinContainer') | ||
var assign = require('object-assign') | ||
@@ -14,127 +14,5 @@ | ||
function getStateFromStore(store, props) { | ||
return typeof store === 'function' ? store(props) : store.getState() | ||
} | ||
function getStateFromActionsProp(actions, props) { | ||
return typeof actions === 'function' ? actions(props) : actions | ||
} | ||
var AltNativeContainer = React.createClass({ | ||
var AltNativeContainer = React.createClass(assign({ | ||
displayName: 'AltNativeContainer', | ||
contextTypes: { | ||
flux: React.PropTypes.object | ||
}, | ||
getInitialState: function () { | ||
if (this.props.stores && this.props.store) { | ||
throw new ReferenceError('Cannot define both store and stores') | ||
} | ||
return this.reduceState(this.props) | ||
}, | ||
componentWillReceiveProps: function (nextProps) { | ||
this.destroySubscriptions() | ||
this.setState(this.reduceState(nextProps)) | ||
this.registerStores(nextProps) | ||
}, | ||
componentDidMount: function () { | ||
this.registerStores(this.props) | ||
}, | ||
componentWillUnmount: function () { | ||
this.destroySubscriptions() | ||
}, | ||
registerStores: function (props) { | ||
Subscribe.create(this) | ||
if (props.store) { | ||
this.addSubscription(props.store) | ||
} else if (props.stores) { | ||
var stores = props.stores | ||
if (Array.isArray(stores)) { | ||
stores.forEach(function (store) { | ||
this.addSubscription(store) | ||
}, this) | ||
} else { | ||
Object.keys(stores).forEach(function (formatter) { | ||
this.addSubscription(stores[formatter]) | ||
}, this) | ||
} | ||
} | ||
}, | ||
destroySubscriptions: function () { | ||
Subscribe.destroy(this) | ||
}, | ||
getStateFromStores: function (props) { | ||
if (props.store) { | ||
return getStateFromStore(props.store, props) | ||
} else if (props.stores) { | ||
var stores = props.stores | ||
// If you pass in an array of stores the state is merged together. | ||
if (Array.isArray(stores)) { | ||
return stores.reduce(function (obj, store) { | ||
return assign(obj, getStateFromStore(store, props)) | ||
}.bind(this), {}) | ||
// if it is an object then the state is added to the key specified | ||
} else { | ||
return Object.keys(stores).reduce(function (obj, key) { | ||
obj[key] = getStateFromStore(stores[key], props) | ||
return obj | ||
}.bind(this), {}) | ||
} | ||
} else { | ||
return {} | ||
} | ||
}, | ||
getStateFromActions: function (props) { | ||
if (props.actions) { | ||
return getStateFromActionsProp(props.actions, props) | ||
} else { | ||
return {} | ||
} | ||
}, | ||
reduceState: function (props) { | ||
return assign( | ||
{}, | ||
this.getStateFromStores(props), | ||
this.getStateFromActions(props) | ||
) | ||
}, | ||
addSubscription: function (store) { | ||
if (typeof store === 'object') { | ||
Subscribe.add(this, store, this.altSetState) | ||
} | ||
}, | ||
altSetState: function () { | ||
this.setState(this.reduceState(this.props)) | ||
}, | ||
getProps: function () { | ||
var flux = this.props.flux || this.context.flux | ||
return assign( | ||
flux ? { flux: flux } : {}, | ||
this.state | ||
) | ||
}, | ||
shouldComponentUpdate: function () { | ||
return this.props.shouldComponentUpdate | ||
? this.props.shouldComponentUpdate(this.getProps()) | ||
: true | ||
}, | ||
render: function () { | ||
@@ -159,4 +37,4 @@ var children = this.props.children | ||
} | ||
}) | ||
}, mixinContainer(React))) | ||
module.exports = AltNativeContainer |
@@ -1142,3 +1142,8 @@ (function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.Alt = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){ | ||
value: function listen(cb) { | ||
var _this8 = this; | ||
this[EE].on("change", cb); | ||
return function () { | ||
return _this8.unlisten(cb); | ||
}; | ||
} | ||
@@ -1148,2 +1153,5 @@ }, | ||
value: function unlisten(cb) { | ||
if (this[LIFECYCLE].unlisten) { | ||
this[LIFECYCLE].unlisten(); | ||
} | ||
this[EE].removeListener("change", cb); | ||
@@ -1150,0 +1158,0 @@ } |
@@ -886,3 +886,8 @@ (function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.Alt = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){ | ||
value: function listen(cb) { | ||
var _this8 = this; | ||
this[EE].on("change", cb); | ||
return function () { | ||
return _this8.unlisten(cb); | ||
}; | ||
} | ||
@@ -892,2 +897,5 @@ }, | ||
value: function unlisten(cb) { | ||
if (this[LIFECYCLE].unlisten) { | ||
this[LIFECYCLE].unlisten(); | ||
} | ||
this[EE].removeListener("change", cb); | ||
@@ -894,0 +902,0 @@ } |
@@ -143,3 +143,8 @@ "use strict"; | ||
value: function listen(cb) { | ||
var _this8 = this; | ||
this[EE].on("change", cb); | ||
return function () { | ||
return _this8.unlisten(cb); | ||
}; | ||
} | ||
@@ -149,2 +154,5 @@ }, | ||
value: function unlisten(cb) { | ||
if (this[LIFECYCLE].unlisten) { | ||
this[LIFECYCLE].unlisten(); | ||
} | ||
this[EE].removeListener("change", cb); | ||
@@ -151,0 +159,0 @@ } |
@@ -155,3 +155,8 @@ "use strict"; | ||
value: function listen(cb) { | ||
var _this8 = this; | ||
this[EE].on("change", cb); | ||
return function () { | ||
return _this8.unlisten(cb); | ||
}; | ||
} | ||
@@ -161,2 +166,5 @@ }, | ||
value: function unlisten(cb) { | ||
if (this[LIFECYCLE].unlisten) { | ||
this[LIFECYCLE].unlisten(); | ||
} | ||
this[EE].removeListener("change", cb); | ||
@@ -163,0 +171,0 @@ } |
@@ -56,3 +56,3 @@ --- | ||
You can pass in a custom function as the value in order to control what each prop will represent. Say you have multiple getters on a store and only want to pass a subset of the state rather than the whole state. | ||
You can pass in a custom function as the value in order to control what each prop will represent. Say you have multiple getters on a store and only want to pass a subset of the state rather than the whole state. These functions must return a special object in order to let AltContainer know which store they'll listen to. | ||
@@ -64,9 +64,18 @@ ```js | ||
post: function (props) { | ||
return BlogStore.getPostFor(props.blogId); | ||
return { | ||
store: BlogStore, | ||
value: BlogStore.getPostFor(props.blogId) | ||
}; | ||
}, | ||
comments: function (props) { | ||
return CommentsStore.getCommentsFor(props.blogId) | ||
return { | ||
store: CommentsStore, | ||
value: CommentsStore.getCommentsFor(props.blogId) | ||
}; | ||
}, | ||
shares: function (props) { | ||
return ShareStore.getSharesFor(props.blogId) | ||
return { | ||
store: ShareStore, | ||
value: ShareStore.getSharesFor(props.blogId) | ||
}; | ||
} | ||
@@ -105,3 +114,6 @@ } | ||
function blogStoreFetcher(props) { | ||
return BlogStore.getPostFor(props.blogId); | ||
return { | ||
store: BlogStore, | ||
value: BlogStore.getPostFor(props.blogId) | ||
}; | ||
} | ||
@@ -108,0 +120,0 @@ |
@@ -122,1 +122,7 @@ --- | ||
``` | ||
## unlisten | ||
> (): undefined | ||
`unlisten` is called when you call unlisten on your store subscription. You can use this method to perform any teardown type tasks. |
@@ -26,5 +26,5 @@ --- | ||
> (handler: function): undefined | ||
> (handler: function): function | ||
The listen method takes a function which will be called when the store emits a change. A change event is emitted automatically whenever a dispatch completes unless you return `false` from the action handler method defined in the StoreModel. | ||
The listen method takes a function which will be called when the store emits a change. A change event is emitted automatically whenever a dispatch completes unless you return `false` from the action handler method defined in the StoreModel. The `listen` method returns a function that you can use to unsubscribe to store updates. | ||
@@ -31,0 +31,0 @@ ```js |
{ | ||
"name": "alt", | ||
"version": "0.15.5", | ||
"version": "0.15.6", | ||
"description": "A flux implementation", | ||
@@ -5,0 +5,0 @@ "main": "dist/alt.js", |
@@ -283,3 +283,3 @@ # alt | ||
`listen` is meant to be used by your View components in order to await changes made to each store. | ||
`listen` is meant to be used by your View components in order to await changes made to each store. It returns a function you can use to un-listen to your store. | ||
@@ -292,3 +292,3 @@ ```js | ||
`unlisten` is a clean up method. It takes in the same function you used for `listen` and unregisters it. | ||
Alternatively, you can use the `unlisten` method. It takes in the same function you used for `listen` and unregisters it. | ||
@@ -678,2 +678,4 @@ `getState` will return a copy of your the current store's state. | ||
[See all the lifecycle methods](http://alt.js.org/docs/lifecycleListeners/) | ||
### Single Dispatcher | ||
@@ -680,0 +682,0 @@ |
import Alt from '../dist/alt-with-runtime' | ||
import { assert } from 'chai' | ||
import sinon from 'sinon' | ||
@@ -552,2 +553,34 @@ import ListenerMixin from '../mixins/ListenerMixin' | ||
'unlistening'() { | ||
assert(myStore.getState().name !== 'moose', 'state has not been updated') | ||
const mooseChecker = sinon.spy() | ||
const unlisten = myStore.listen(mooseChecker) | ||
myActions.updateName('moose') | ||
assert(myStore.getState().name === 'moose', 'new store state present') | ||
unlisten() | ||
myActions.updateName('badger') | ||
assert(myStore.getState().name === 'badger', 'new store state present') | ||
assert.ok(mooseChecker.calledOnce) | ||
}, | ||
'unlisten lifecycle hook'() { | ||
const unlistener = sinon.spy() | ||
class XStore { | ||
constructor() { | ||
this.on('unlisten', unlistener) | ||
} | ||
} | ||
const store = alt.createStore(XStore) | ||
// unlisten directly | ||
store.listen()() | ||
assert.ok(unlistener.calledOnce, 'unlisten lifecycle hook called') | ||
}, | ||
'bootstrapping'() { | ||
@@ -554,0 +587,0 @@ alt.bootstrap('{"MyStore":{"name":"bee"}}') |
@@ -268,7 +268,13 @@ import Alt from '../dist/alt-with-runtime' | ||
function a() { | ||
return { x: 'test' } | ||
return { | ||
store: TestStore, | ||
value: { x: 'test' } | ||
} | ||
} | ||
function b() { | ||
return { y: 'test2' } | ||
return { | ||
store: TestStore, | ||
value: { y: 'test2' } | ||
} | ||
} | ||
@@ -302,3 +308,8 @@ | ||
const node = TestUtils.renderIntoDocument( | ||
<AltContainer store={() => { return { x: 'jesting' } }}> | ||
<AltContainer store={() => { | ||
return { | ||
store: TestStore, | ||
value: { x: 'jesting' } | ||
} | ||
}}> | ||
<span /> | ||
@@ -313,5 +324,10 @@ </AltContainer> | ||
'function is called with props'() { | ||
const Store = sinon.spy() | ||
const storeFunction = sinon.stub() | ||
storeFunction.returns({ | ||
store: TestStore, | ||
value: {} | ||
}) | ||
TestUtils.renderIntoDocument( | ||
<AltContainer className="foo" store={Store}> | ||
<AltContainer className="foo" store={storeFunction}> | ||
<span /> | ||
@@ -321,6 +337,9 @@ </AltContainer> | ||
assert.ok(Store.calledOnce) | ||
assert(Store.args[0].length === 1, 'called with one parameter') | ||
assert.isObject(Store.args[0][0], 'called with the props') | ||
assert(Store.args[0][0].className === 'foo', 'props match') | ||
assert.ok(storeFunction.calledTwice, 'called twice, once for store listening and another for props') | ||
assert(storeFunction.args[0].length === 1, 'called with one parameter') | ||
assert(storeFunction.args[1].length === 1, 'called with one parameter') | ||
assert.isObject(storeFunction.args[0][0], 'called with the props') | ||
assert.isObject(storeFunction.args[1][0], 'called with the props') | ||
assert(storeFunction.args[0][0].className === 'foo', 'props match') | ||
assert(storeFunction.args[1][0].className === 'foo', 'props match') | ||
}, | ||
@@ -331,6 +350,12 @@ | ||
x() { | ||
return { a: 'hello' } | ||
return { | ||
store: TestStore, | ||
value: { a: 'hello' } | ||
} | ||
}, | ||
y() { | ||
return { b: 'goodbye' } | ||
return { | ||
store: TestStore, | ||
value: { b: 'goodbye' } | ||
} | ||
} | ||
@@ -337,0 +362,0 @@ } |
364748
82
827
7467