Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add connectFilter mixin #222

Merged
merged 1 commit into from Feb 4, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
21 changes: 21 additions & 0 deletions README.md
Expand Up @@ -501,6 +501,27 @@ var Status = React.createClass({
});
```

#### Using Reflux.connectFilter

`Reflux.connectFilter` is used in a similar manner to `Reflux.connect`. Use the
`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) {
posts.filter(function(post) {
post.id === this.props.id;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

am I wrong or are one or two return statements missing here?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yup... nice catch.

I've been having too much coffeescript lately to notice. 👍

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Heh, that was my problem too.
On Mon, Feb 16, 2015 at 11:25 PM Mikael Brassman notifications@github.com
wrote:

In README.md
#222 (comment):

@@ -501,6 +501,27 @@ var Status = React.createClass({
});


+#### Using Reflux.connectFilter
+
+`Reflux.connectFilter` is used in a similar manner to `Reflux.connect`. Use the
+`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) {
+        posts.filter(function(post) {
+           post.id === this.props.id;

Yup... nice catch.

I've been having too much coffeescript lately to notice. [image: 👍]


Reply to this email directly or view it on GitHub
https://github.com/spoike/refluxjs/pull/222/files#r24797357.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

seems to be fixed now by #240

});
}],
render: function() {
// render using `this.state.post`
}
});
```

### Listening to changes in other data stores (aggregate data stores)

A store may listen to another store's change, making it possible to safely chain stores for aggregated data without affecting other parts of the application. A store may listen to other stores using the same `listenTo` function as with actions:
Expand Down
39 changes: 39 additions & 0 deletions src/connectFilter.js
@@ -0,0 +1,39 @@
var Reflux = require('./index'),
_ = require('./utils');

module.exports = function(listenable, key, filterFunc) {
filterFunc = _.isFunction(key) ? key : filterFunc;
return {
getInitialState: function() {
if (!_.isFunction(listenable.getInitialState)) {
return {};
} else if (_.isFunction(key)) {
return filterFunc.call(this, listenable.getInitialState());
} else {
// Filter initial payload from store.
var result = filterFunc.call(this, listenable.getInitialState());
if (result) {
return _.object([key], [result]);
} else {
return {};
}
}
},
componentDidMount: function() {
_.extend(this, Reflux.ListenerMethods);
var me = this;
var cb = function(value) {
if (_.isFunction(key)) {
me.setState(filterFunc.call(me, value));
} else {
var result = filterFunc.call(me, value);
me.setState(_.object([key], [result]));
}
};

this.listenTo(listenable, cb);
},
componentWillUnmount: Reflux.ListenerMixin.componentWillUnmount
};
};

2 changes: 2 additions & 0 deletions src/index.js
Expand Up @@ -12,6 +12,8 @@ exports.createStore = require('./createStore');

exports.connect = require('./connect');

exports.connectFilter = require('./connectFilter');

exports.ListenerMixin = require('./ListenerMixin');

exports.listenTo = require('./listenTo');
Expand Down
102 changes: 102 additions & 0 deletions test/usingConnectFilterMixin.spec.js
@@ -0,0 +1,102 @@
var assert = require('chai').assert,
sinon = require('sinon'),
connectFilter = require('../src/connectFilter'),
_ = require('../src/utils'),
Reflux = require('../src');

var dummyFilter = function(value) { return value.slice(0,2); };

describe('using the connectFilter(...) mixin',function(){

it("should be exposed in Reflux",function(){
assert.equal(connectFilter, Reflux.connectFilter);
});

describe("when calling with action",function() {
var listenable = {
listen: sinon.spy()
},
context = {setState: sinon.spy()};
_.extend(context,connectFilter(listenable, dummyFilter));

it("should pass empty object to state",function(){
assert.deepEqual({},context.getInitialState());
});
});

describe("when calling without key",function(){
var initialstate = "DEFAULTDATA",
listenable = {
listen: sinon.spy(),
getInitialState: sinon.stub().returns(initialstate)
},
context = {setState: sinon.spy()},
result = _.extend(context,connectFilter(listenable, dummyFilter));

it("should add getInitialState and componentDidMount and WillUnmount",function(){
assert.isFunction(context.getInitialState);
assert.isFunction(context.componentDidMount);
assert.isFunction(context.componentWillUnmount);
assert.equal(context.componentWillUnmount,Reflux.ListenerMethods.stopListeningToAll);
});

it("should pass initial state to state",function(){
assert.deepEqual(initialstate.slice(0,2),context.getInitialState());
});

result.componentDidMount();

it("should call listen on the listenable correctly",function(){
assert.equal(1,listenable.listen.callCount);
assert.isFunction(listenable.listen.firstCall.args[0]);
assert.equal(context,listenable.listen.firstCall.args[1]);
});

it("should store the subscription object correctly",function(){
assert.equal(listenable,context.subscriptions[0].listenable);
});

});

describe("when calling with key",function(){
var initialstate = "DEFAULTDATA",
triggerdata = "TRIGGERDATA",
key = "KEY",
listenable = {
listen: sinon.spy(),
getInitialState: sinon.stub().returns(initialstate)
},
context = {setState: sinon.spy()},
result = _.extend(context,connectFilter(listenable,key,dummyFilter));

it("should pass initial state to state correctly",function(){
assert.deepEqual({KEY:initialstate.slice(0,2)},context.getInitialState());
});

result.componentDidMount();

it("should call listen on the listenable correctly",function(){
assert.equal(1,listenable.listen.callCount);
assert.isFunction(listenable.listen.firstCall.args[0]);
assert.equal(context,listenable.listen.firstCall.args[1]);
});

it("should send listenable callback which calls setState correctly",function(){
listenable.listen.firstCall.args[0](triggerdata);
assert.deepEqual([_.object([key],[triggerdata.slice(0,2)])],context.setState.firstCall.args);
});
});
describe("when calling with falsy key",function(){
var triggerdata = "TRIGGERDATA",
key = 0,
listenable = {listen: sinon.spy()},
context = {setState: sinon.spy()},
result = _.extend(context,connectFilter(listenable,key,dummyFilter));
result.componentDidMount();
it("should send listenable callback which calls setState correctly",function(){
listenable.listen.firstCall.args[0](triggerdata);
assert.deepEqual([_.object([key],[triggerdata.slice(0,2)])],context.setState.firstCall.args);
});
});
});