From 1bd311269b36d3de94d8cddc88f6db61ff797e4c Mon Sep 17 00:00:00 2001 From: Josh Perez Date: Fri, 10 Apr 2015 13:09:04 -0700 Subject: [PATCH] Pass in actions into AltContainer --- components/AltContainer.js | 34 +++++++++++---- dist/alt-browser-with-addons.js | 4 +- docs/components/altContainer.md | 59 +++++++++++++++++++++++++++ test/store-listener-component-test.js | 45 ++++++++++++++++++++ 4 files changed, 133 insertions(+), 9 deletions(-) diff --git a/components/AltContainer.js b/components/AltContainer.js index 98c53f68..b2f42351 100644 --- a/components/AltContainer.js +++ b/components/AltContainer.js @@ -43,22 +43,26 @@ var assign = require('object-assign') var cloneWithProps = React.addons.cloneWithProps -function getState(store, props) { +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({ getInitialState: function () { if (this.props.stores && this.props.store) { throw new ReferenceError('Cannot define both store and stores') } - return this.getStateFromStores(this.props) || {} + return this.reduceState(this.props) }, componentWillReceiveProps: function (nextProps) { this.destroySubscriptions() - this.setState(this.getStateFromStores(nextProps)) + this.setState(this.reduceState(nextProps)) this.registerStores(nextProps) }, @@ -96,20 +100,20 @@ var AltContainer = React.createClass({ getStateFromStores: function (props) { if (props.store) { - return getState(props.store, props) + 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, getState(store, props)) + 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] = getState(stores[key], props) + obj[key] = getStateFromStore(stores[key], props) return obj }.bind(this), {}) } @@ -118,6 +122,22 @@ var AltContainer = React.createClass({ } }, + 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) @@ -125,7 +145,7 @@ var AltContainer = React.createClass({ }, altSetState: function () { - this.setState(this.getStateFromStores(this.props)) + this.setState(this.reduceState(this.props)) }, getProps: function () { diff --git a/dist/alt-browser-with-addons.js b/dist/alt-browser-with-addons.js index d549e809..04c3240e 100644 --- a/dist/alt-browser-with-addons.js +++ b/dist/alt-browser-with-addons.js @@ -1856,13 +1856,13 @@ DispatcherRecorder.prototype.loadEvents = function (events) { module.exports = makeFinalStore; function FinalStore() { - this.dispatcher.register((function () { + this.dispatcher.register((function (payload) { var stores = Object.keys(this.alt.stores).reduce((function (arr, store) { return (arr.push(this.alt.stores[store].dispatchToken), arr); }).bind(this), []); this.waitFor(stores); - this.getInstance().emitChange(); + this.setState({ payload: payload }); }).bind(this)); } diff --git a/docs/components/altContainer.md b/docs/components/altContainer.md index 4d565919..e6ce2448 100644 --- a/docs/components/altContainer.md +++ b/docs/components/altContainer.md @@ -11,6 +11,8 @@ AltContainer is not an idea exclusive to alt. For more information on why you sh The basic idea is that you have a container that wraps your component, the duty of this container component is to handle all the data fetching and communication with the stores, it then renders the corresponding children. The sub-components just render markup and are data agnostic thus making them highly reusable. +AltContainer doesn't just wrap your dumb components into a high-performance store listener but it also serves as a jack-of-all-trades component where you can directly inject any dependencies into your components such as stores, actions, or the flux context. + ## `stores` You can pass in an object to `stores` where the keys correspond to the prop that the children will receive and their value the store we should retrieve the state from. @@ -96,6 +98,63 @@ Children will get the properties you define. ``` +## `actions` + +You can directly inject any actions you wish to make available to your child components which will then be available via their props. + +For example, say you have a pretty standard `BlogActions`. + +```js +var BlogActions = alt.generateActions('makePost'); +``` + +You can inject these actions into BlogPost like so: + +```js + + + +``` + +The actions found in `BlogActions` will now be available in an object you defined `MyActions`. So you may call `this.props.MyActions.makePost()` and the `BlogActions.makePost` action will be fired. + +You can inject these actions directly on the props themselves: + +```js + + + +``` + +`BlogPost` here will receive all of the state of `BlogStore` as props, and all of the actions of `BlogActions`. So you may call `this.props.makePost()` and the `BlogActions.makePost` action will be fired. + +Similar to stores and store the actions prop also accepts a function where you can customize the behavior of the actions: + +```js + + + +``` + ## `render` But you can also specify a special `render` function which will render whatever your heart desires. diff --git a/test/store-listener-component-test.js b/test/store-listener-component-test.js index d64983f3..ce607753 100644 --- a/test/store-listener-component-test.js +++ b/test/store-listener-component-test.js @@ -402,5 +402,50 @@ export default { assert(node.refs.test.props.store === Store2, 'node changes props properly') }, + + 'inject actions'() { + const node = TestUtils.renderIntoDocument( + + + + ) + + const span = TestUtils.findRenderedDOMComponentWithTag(node, 'span') + + assert.isObject(span.props.MyActions, 'MyActions exist') + assert(span.props.MyActions === action, 'MyActions is injected actions') + assert.isFunction(span.props.MyActions.sup, 'sup action is available') + }, + + 'inject all actions directly shorthand'() { + const node = TestUtils.renderIntoDocument( + + + + ) + + const span = TestUtils.findRenderedDOMComponentWithTag(node, 'span') + + assert.isFunction(span.props.sup, 'sup is available directly on the props') + }, + + 'inject all actions using a function'() { + const node = TestUtils.renderIntoDocument( + + + + ) + + const span = TestUtils.findRenderedDOMComponentWithTag(node, 'span') + + assert.isObject(span.props.FooActions, 'actions are injected') + assert.isFunction(span.props.FooActions.sup, 'sup is available') + }, } }