Skip to content

Commit

Permalink
Pass in actions into AltContainer
Browse files Browse the repository at this point in the history
  • Loading branch information
goatslacker committed Apr 10, 2015
1 parent 631f883 commit 1bd3112
Show file tree
Hide file tree
Showing 4 changed files with 133 additions and 9 deletions.
34 changes: 27 additions & 7 deletions components/AltContainer.js
Original file line number Diff line number Diff line change
Expand Up @@ -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)
},

Expand Down Expand Up @@ -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), {})
}
Expand All @@ -118,14 +122,30 @@ 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)
}
},

altSetState: function () {
this.setState(this.getStateFromStores(this.props))
this.setState(this.reduceState(this.props))
},

getProps: function () {
Expand Down
4 changes: 2 additions & 2 deletions dist/alt-browser-with-addons.js
Original file line number Diff line number Diff line change
Expand Up @@ -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));
}

Expand Down
59 changes: 59 additions & 0 deletions docs/components/altContainer.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -96,6 +98,63 @@ Children will get the properties you define.
</AltContainer>
```

## `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
<AltContainer
store={BlogStore}
actions={{ MyActions: BlogActions }}
/>
<BlogPost className="my-awesome-post" />
</AltContainer>
```

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
<AltContainer
store={BlogStore}
actions={BlogActions}
/>
<BlogPost className="my-awesome-post" />
</AltContainer>
```

`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
<AltContainer
store={BlogStore}
actions={function (props) {
return {
makePost: function (postText) {
// trim the post first
postText.trim()

// then call the action
return BlogActions.makePost(postText)
}
}
}}
/>
<BlogPost className="my-awesome-post" />
</AltContainer>
```

## `render`

But you can also specify a special `render` function which will render whatever your heart desires.
Expand Down
45 changes: 45 additions & 0 deletions test/store-listener-component-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -402,5 +402,50 @@ export default {

assert(node.refs.test.props.store === Store2, 'node changes props properly')
},

'inject actions'() {
const node = TestUtils.renderIntoDocument(
<AltContainer actions={{ MyActions: action }}>
<span />
</AltContainer>
)

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(
<AltContainer actions={action}>
<span />
</AltContainer>
)

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(
<AltContainer actions={function (props) {
return {
FooActions: {
sup: action.sup.bind(action)
}
}
}}>
<span />
</AltContainer>
)

const span = TestUtils.findRenderedDOMComponentWithTag(node, 'span')

assert.isObject(span.props.FooActions, 'actions are injected')
assert.isFunction(span.props.FooActions.sup, 'sup is available')
},
}
}

0 comments on commit 1bd3112

Please sign in to comment.