Skip to content

Commit 51e3b3e

Browse files
committed
feat(dataResolver): Added data resolver
Added ability to pass in a custom function to resolve the entitiy data. Added signifcant amount o docs on usage and why. Added default data resolver that checks for FSA then assume action has entities property. Moved to using redux combineReducers for custom reducers
1 parent ce41b4f commit 51e3b3e

File tree

6 files changed

+163
-14
lines changed

6 files changed

+163
-14
lines changed

README.md

Lines changed: 42 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
1-
# Entities reducer
2-
3-
Redux high order reducer for normalized `flux-standard-action`s
1+
# Entities Reducer
42

53
[![Build Status][build-badge]][build]
64
[![Code Coverage][coverage-badge]][coverage]
@@ -10,11 +8,27 @@ Redux high order reducer for normalized `flux-standard-action`s
108
[![Watch on GitHub][github-watch-badge]][github-watch]
119
[![Star on GitHub][github-star-badge]][github-star]
1210

11+
This package is a high-order reducer that updates state using entities from [normalizr](https://github.com/paularmstrong/normalizr).
12+
By default, it expects the action to have a property of `entities` or to follow the [`flux-standard-action` spec](https://github.com/acdlite/flux-standard-action).
13+
However, it is possible to pass a data resolver ([see Parameters](#parameter-data-resolver)) if your case doesn't match either of those.
14+
15+
## Why
16+
**Why does this package exist?**
1317

14-
Allows for updates to entities in state. Accepts custom reducers to further control.
18+
One of the best things to store in redux is data from server requests. Additionally, working with items stored in redux is best done when the data is normalized.
19+
To simplify the storing of the data, this package will handle updating state with fresh normalized data.
20+
21+
Entities Reducer is a high-order reducer, which means it will accept more reducers for further customizations.
22+
The custom reducers are passed directly to `combineRecuders` from redux and should be passed into `entities-reducers` with the same format.
1523

16-
## Usage
24+
## Installation
25+
```
26+
npm install --save entities-reducer
27+
--- or ---
28+
yarn add entities-reducer
29+
```
1730

31+
Then add to your root reducer:
1832
```javascript
1933
import { combineReducers } from 'redux';
2034
import entitiesReducer from 'entities-reducer';
@@ -28,6 +42,29 @@ const rootReducer = combineReducers({
2842
export default rootReducer;
2943
```
3044

45+
## Parameters
46+
47+
```javascript
48+
entitiesReducer(reducers, { dataResolver })
49+
```
50+
51+
### (#parameters-reducers) Reducers
52+
Reducers are passed directly into `combineReducers` from redux, after the entities have been updated in state. It is called with the updated state and immediately returned.
53+
54+
### (#parameters-data-resolver) dataResolver
55+
The data resolver is a lookup function that is passed the action and returns the entities object to use while updating.
56+
If the data resolver returns a falsy value the `entities-reducer` will skip process and move directly to handling the custom reducers.
57+
Below is a customer dataResolver example, or you can checkout the [default resolver](src/index.js).
58+
**Example**
59+
```javascript
60+
const customDataResolver = (action) => {
61+
if (action.error) {
62+
return false;
63+
}
64+
return action.data.extra.normalized;
65+
}
66+
```
67+
3168

3269
[build]: https://travis-ci.org/kwelch/entities-reducer
3370
[build-badge]: https://img.shields.io/travis/kwelch/entities-reducer.svg?style=flat-square

package.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,5 +45,12 @@
4545
"es6": true,
4646
"jest": true
4747
}
48+
},
49+
"peerDependencies": {
50+
"redux": "^3.3.0"
51+
},
52+
"dependencies": {
53+
"flux-standard-action": "^1.1.0",
54+
"redux": "3.0.3"
4855
}
4956
}

src/__snapshots__/index.spec.js.snap

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,32 @@ Object {
44
}
55
`;
66

7+
exports[`entitiesReducer should allow for custom data resolver 1`] = `
8+
Object {
9+
"users": Object {
10+
"1": Object {
11+
"firstName": "Kyle",
12+
"id": 1,
13+
"lastName": "Welch",
14+
"middleName": "Ryan",
15+
},
16+
},
17+
}
18+
`;
19+
20+
exports[`entitiesReducer should allow for non-fsa actions 1`] = `
21+
Object {
22+
"users": Object {
23+
"1": Object {
24+
"firstName": "Kyle",
25+
"id": 1,
26+
"lastName": "Welch",
27+
"middleName": "Ryan",
28+
},
29+
},
30+
}
31+
`;
32+
733
exports[`entitiesReducer should reduce entities from payload 1`] = `
834
Object {
935
"users": Object {

src/index.js

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
import { isFSA } from 'flux-standard-action';
2+
import { combineReducers } from 'redux';
3+
14
const updateEntity = (state, entities) => {
25
return Object.assign({}, state, entities);
36
};
@@ -6,16 +9,25 @@ const updateEntities = (state, key, entities) => {
69
return Object.assign({}, state, { [key]: updateEntity(state[key], entities[key]) });
710
};
811

9-
export default (reducers) => (state = {}, action) => {
10-
const { payload } = action;
12+
const defaultDataResolver = (action) => {
13+
if (isFSA(action)) {
14+
const { payload } = action;
15+
if (payload && !payload.error && payload.entities) {
16+
return payload.entities;
17+
}
18+
return null;
19+
}
20+
return action.entities;
21+
};
22+
23+
export default (reducers, { dataResolver } = { dataResolver: defaultDataResolver }) => (state = {}, action) => {
1124
let newState = state;
12-
if (payload && !payload.error && payload.entities) {
13-
const { entities } = payload;
25+
const entities = dataResolver(action);
26+
if (entities && typeof entities === 'object') {
1427
newState = Object.keys(entities).reduce((acc, key) => updateEntities(acc, key, entities), newState);
1528
}
16-
newState = Object.keys(reducers).reduce((acc, key) => {
17-
const reducerReturn = reducers[key](acc[key], action);
18-
return Object.assign({}, acc, { [key]: reducerReturn });
19-
}, newState);
29+
if (reducers && typeof reducers === 'object' && Object.keys(reducers).length > 0) {
30+
return combineReducers(reducers)(newState, action);
31+
}
2032
return newState;
2133
};

src/index.spec.js

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,13 +110,14 @@ describe('entitiesReducer', () => {
110110

111111
it('should allow customer reducers ', () => {
112112
const customUserReducer = (state = {}, action) => {
113-
switch(action.type) {
113+
switch (action.type) {
114114
case "ENTITY/DELETE": {
115115
const newState = Object.assign({}, state);
116116
delete newState[action.payload.result];
117117
return newState;
118118
}
119119
}
120+
return state;
120121
};
121122
let newState = entitiesReducer({ users: customUserReducer, })(newState, {
122123
payload: {
@@ -139,4 +140,46 @@ describe('entitiesReducer', () => {
139140
});
140141
expect(newState).toMatchSnapshot();
141142
});
143+
144+
it('should allow for non-fsa actions', () => {
145+
let newState = entitiesReducer({})(initialState, {
146+
entities: {
147+
users: {
148+
1: {
149+
firstName: "Kyle",
150+
id: 1,
151+
lastName: "Welch",
152+
middleName: "Ryan",
153+
},
154+
},
155+
},
156+
result: 1,
157+
type: "ENTITY/ENTITY_NORMALIZE",
158+
});
159+
expect(newState).toMatchSnapshot();
160+
});
161+
162+
163+
it('should allow for custom data resolver', () => {
164+
const dataResolver = (action) => {
165+
return action.deeplyNested.notEntities;
166+
};
167+
let newState = entitiesReducer({}, { dataResolver })(initialState, {
168+
deeplyNested: {
169+
notEntities: {
170+
users: {
171+
1: {
172+
firstName: "Kyle",
173+
id: 1,
174+
lastName: "Welch",
175+
middleName: "Ryan",
176+
},
177+
},
178+
},
179+
result: 1,
180+
},
181+
type: "ENTITY/ENTITY_NORMALIZE",
182+
});
183+
expect(newState).toMatchSnapshot();
184+
});
142185
});

yarn.lock

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1388,6 +1388,14 @@ flat-cache@^1.2.1:
13881388
graceful-fs "^4.1.2"
13891389
write "^0.2.1"
13901390

1391+
flux-standard-action@^1.1.0:
1392+
version "1.1.0"
1393+
resolved "https://registry.yarnpkg.com/flux-standard-action/-/flux-standard-action-1.1.0.tgz#114a014e70c8830505129511e043880f0961fb38"
1394+
dependencies:
1395+
lodash.isplainobject "^4.0.6"
1396+
lodash.isstring "^4.0.1"
1397+
lodash.issymbol "^4.0.1"
1398+
13911399
for-in@^0.1.5:
13921400
version "0.1.6"
13931401
resolved "https://registry.yarnpkg.com/for-in/-/for-in-0.1.6.tgz#c9f96e89bfad18a545af5ec3ed352a1d9e5b4dc8"
@@ -2322,6 +2330,18 @@ lodash.isarray@^3.0.0:
23222330
version "3.0.4"
23232331
resolved "https://registry.yarnpkg.com/lodash.isarray/-/lodash.isarray-3.0.4.tgz#79e4eb88c36a8122af86f844aa9bcd851b5fbb55"
23242332

2333+
lodash.isplainobject@^4.0.6:
2334+
version "4.0.6"
2335+
resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb"
2336+
2337+
lodash.isstring@^4.0.1:
2338+
version "4.0.1"
2339+
resolved "https://registry.yarnpkg.com/lodash.isstring/-/lodash.isstring-4.0.1.tgz#d527dfb5456eca7cc9bb95d5daeaf88ba54a5451"
2340+
2341+
lodash.issymbol@^4.0.1:
2342+
version "4.0.1"
2343+
resolved "https://registry.yarnpkg.com/lodash.issymbol/-/lodash.issymbol-4.0.1.tgz#04ad41d96f3f4f399c37dd4fcf3c1b6901e16116"
2344+
23252345
lodash.keys@^3.0.0:
23262346
version "3.1.2"
23272347
resolved "https://registry.yarnpkg.com/lodash.keys/-/lodash.keys-3.1.2.tgz#4dbc0472b156be50a0b286855d1bd0b0c656098a"
@@ -2933,6 +2953,10 @@ redeyed@~1.0.0:
29332953
dependencies:
29342954
esprima "~3.0.0"
29352955

2956+
redux@3.0.3:
2957+
version "3.0.3"
2958+
resolved "https://registry.yarnpkg.com/redux/-/redux-3.0.3.tgz#cf60cc323ca00fcd15fe76197232df3dc32f568f"
2959+
29362960
regenerate@^1.2.1:
29372961
version "1.3.2"
29382962
resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.3.2.tgz#d1941c67bad437e1be76433add5b385f95b19260"

0 commit comments

Comments
 (0)