Skip to content
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 10 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,12 @@ the API is straightforward:

```JS
$ngRedux.connect(selector, callback, disableCaching = false);
//OR
$ngRedux.connect([selector1, selector2, ...], callback, disableCaching = false);
```

Where '''selector''' is a function taking for single argument the entire redux Store's state (a plain JS object) and returns another object, which is the slice of the state that your component is interested in.
Where `selector` is a function that takes Redux's entire store state as argument and returns an object that contains the slices of store state that your component is interested in.
e.g:
```JS
state => state.todos
state => ({todos: state.todos})
```
Note: if you are not familiar with this syntax, go and check out the [MDN Guide on fat arrow functions (ES2015)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions)

Expand Down Expand Up @@ -77,7 +75,7 @@ class TodoLoaderController {

constructor($ngRedux) {
this.todos = [];
$ngRedux.connect(state => state.todos, todos => this.todos = todos);
$ngRedux.connect(state => ({todos: state.todos}), ({todos}) => this.todos = todos);
}

[...]
Expand All @@ -94,11 +92,11 @@ You can also grab multiple slices of the state by passing an array of selectors:
constructor(reduxConnector) {
this.todos = [];
this.users = [];
$ngRedux.connect([
state => state.todos,
state => state.users
],
(todos, users) => {
$ngRedux.connect(state => ({
todos: state.todos,
users: state.users
}),
({todos, users}) => {
this.todos = todos
this.users = users;
});
Expand All @@ -114,7 +112,7 @@ You can close a connection like this:

constructor(reduxConnector) {
this.todos = [];
this.unsubscribe = reduxConnector.connect(state => state.todos, todos => this.todos = todos);
this.unsubscribe = reduxConnector.connect(state => ({todos: state.todos}), ({todos}) => this.todos = todos);
}

destroy() {
Expand All @@ -137,7 +135,7 @@ Each time Redux's Store update, ng-redux will check if the slices specified via
You can disable this behaviour, and force the callback to be executed even if the slices didn't change by setting ```disableCaching``` to true:

```JS
reduxConnector.connect(state => state.todos, todos => this.todos = todos, true);
reduxConnector.connect(state => ({todos: state.todos}), ({todos}) => this.todos = todos, true);
```


Expand Down
14 changes: 5 additions & 9 deletions src/components/connector.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,25 +4,21 @@ import invariant from 'invariant';

export default function Connector(store) {
return {
connect: (selectors, callback, disableCaching = false) => {
if (!Array.isArray(selectors)) {
selectors = [selectors];
}

connect: (selector, callback, disableCaching = false) => {
invariant(
isFunction(callback),
'The callback parameter passed to connect must be a Function. Instead received %s.',
typeof callback
);

//Initial update
let params = selectors.map(selector => selector(store.getState()));
callback(...params);
let params = selector(store.getState());
callback(params);

let unsubscribe = store.subscribe(() => {
let nextParams = selectors.map(selector => selector(store.getState()));
let nextParams = selector(store.getState());
if (disableCaching || !shallowEqual(params, nextParams)) {
callback(...nextParams);
callback(nextParams);
params = nextParams;
}
});
Expand Down
34 changes: 14 additions & 20 deletions test/components/connector.spec.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import expect from 'expect';
import {createStore} from 'redux';
import {default as Connector, copyArray} from '../../src/components/connector';
import Connector from '../../src/components/connector';

describe('Connector', () => {
let store;
Expand All @@ -25,7 +25,7 @@ describe('Connector', () => {
expect(counter).toBe(1);
});

it('Should call the function passed to connect when the store updates', () => {
it('Should call the callback passed to connect when the store updates', () => {
let counter = 0;
let callback = () => counter++;
connector.connect(state => state, callback);
Expand All @@ -34,18 +34,10 @@ describe('Connector', () => {
expect(counter).toBe(3);
});

it('Should accept a function or an array of function as selector', () => {
let receivedState1, receivedState2;
connector.connect(state => state.foo, newState => receivedState1 = newState);
connector.connect([state => state.foo], newState => receivedState2 = newState);
expect(receivedState1).toBe('bar');
expect(receivedState1).toBe(receivedState2);
})

it('Should prevent unnecessary updates when state does not change (shallowly)', () => {
let counter = 0;
let callback = () => counter++;
connector.connect(state => state.baz, callback);
connector.connect(state => ({baz: state.baz}), callback);
store.dispatch({type: 'ACTION', payload: 0});
store.dispatch({type: 'ACTION', payload: 0});
store.dispatch({type: 'ACTION', payload: 1});
Expand All @@ -55,7 +47,7 @@ describe('Connector', () => {
it('Should disable caching when disableCaching is set to true', () => {
let counter = 0;
let callback = () => counter++;
connector.connect(state => state.baz, callback, true);
connector.connect(state => ({baz: state.baz}), callback, true);
store.dispatch({type: 'ACTION', payload: 0});
store.dispatch({type: 'ACTION', payload: 0});
store.dispatch({type: 'ACTION', payload: 1});
Expand All @@ -64,18 +56,20 @@ describe('Connector', () => {

it('Should pass the selected state as argument to the callback', () => {
let receivedState;
connector.connect(state => state.foo, newState => receivedState = newState);
store.dispatch({type: 'ACTION', payload: 1});
expect(receivedState).toBe('bar');
connector.connect(state => ({
myFoo: state.foo
}), newState => receivedState = newState);
expect(receivedState).toEqual({myFoo: 'bar'});
});

it('Should pass all the selected state as argument to the callback when provided an array of selectors', () => {
connector.connect([state => state.foo, state => state.anotherState],
(foo, anotherState) => {
it('Should allow multiple store slices to be selected', () => {
connector.connect(state => ({
foo: state.foo,
anotherState: state.anotherState
}), ({foo, anotherState}) => {
expect(foo).toBe('bar');
expect(anotherState).toBe(12);
});
store.dispatch({type: 'ACTION', payload: 1});
});
});

it('Should return an unsubscribing function', () => {
Expand Down