Skip to content

Commit 32b47ad

Browse files
authored
Merge pull request #11 from log-oscon/feature/public-denormalize
Expose a denormalization for selectors
2 parents 8ad8c40 + 1b59f20 commit 32b47ad

File tree

7 files changed

+65
-7
lines changed

7 files changed

+65
-7
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ Some guidelines in reading this document:
1212

1313
## [new release]
1414

15+
* Expose a denormalization mechanism so consumer can transform local ids into resources denormalized ([#11](https://github.com/log-oscon/redux-wpapi/pull/11))
1516
* Fix the Promise return from middleware dispatch, which should always resolve to `selectQuery` result ([#9](https://github.com/log-oscon/redux-wpapi/pull/9))
1617

1718
## 1.0.1

src/ReduxWPAPI.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import isArray from 'lodash/isArray';
1010
import isUndefined from 'lodash/isUndefined';
1111
import { selectQuery } from './selectors';
1212
import WPAPIAdapter from './adapters/wpapi';
13+
import { lastCacheUpdate as lastCacheUpdateSymbol } from './symbols';
1314

1415
import {
1516
REDUX_WP_API_CALL,
@@ -87,7 +88,7 @@ export default class ReduxWPAPI {
8788
}
8889

8990
if (cache) {
90-
lastCacheUpdate = cache.lastCacheUpdate;
91+
lastCacheUpdate = cache[lastCacheUpdateSymbol];
9192
} else {
9293
cache = state.getIn(['requestsByQuery', payload.cacheID, payload.page]);
9394
data = state.get('data');
@@ -329,9 +330,9 @@ export default class ReduxWPAPI {
329330
let resourceTransformed = {
330331
...oldState,
331332
...resource,
333+
...meta,
332334
_links,
333335
_embedded,
334-
lastCacheUpdate: meta.lastCacheUpdate,
335336
};
336337

337338
if (this.adapter.transformResource) {

src/index.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,5 @@ import ReduxWPAPI from './ReduxWPAPI';
33
export default ReduxWPAPI;
44
export * as RequestStatus from './constants/requestStatus';
55
export callAPI from './actions/callAPI';
6-
export { selectQuery } from './selectors';
6+
export * as Symbols from './symbols';
7+
export { selectQuery, withDenormalize } from './selectors';

src/selectors.js

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,21 @@
1+
import isFunction from 'lodash/isFunction';
12
import { createSelector } from 'reselect';
3+
24
import { pending } from './constants/requestStatus';
35
import { mapDeep } from './helpers';
6+
import { id as idSymbol } from './symbols';
47

58
export const denormalize = (resources, id, memoized = {}) => {
69
/* eslint-disable no-param-reassign, no-underscore-dangle */
710
if (memoized[id]) return memoized[id];
811

912
const resource = resources.get(id);
13+
if (!resource) {
14+
return null;
15+
}
16+
1017
memoized[id] = {
18+
[idSymbol]: id,
1119
...resource,
1220
...mapDeep(resource._embedded || {},
1321
embeddedId => denormalize(resources, embeddedId, memoized)
@@ -19,6 +27,20 @@ export const denormalize = (resources, id, memoized = {}) => {
1927

2028
export const localResources = state => state.wp.getIn(['resources']);
2129

30+
export const withDenormalize = thunk =>
31+
createSelector(
32+
localResources,
33+
thunk,
34+
(resources, target) => {
35+
if (!isFunction(target)) {
36+
return target;
37+
}
38+
39+
const memo = {};
40+
return target(id => denormalize(resources, id, memo));
41+
}
42+
);
43+
2244
export const selectQuery = name => createSelector(
2345
createSelector(
2446
state => state.wp.getIn(['requestsByName', name]),

src/symbols.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export const id = Symbol('ResourceLocalID');
2+
export const lastCacheUpdate = Symbol('lastCacheUpdate');

test/middleware.spec.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import { createFakeStore } from './mocks/store';
1414
import { initialReducerState } from '../src/ReduxWPAPI';
1515
import { REDUX_WP_API_CALL, REDUX_WP_API_CACHE_HIT } from '../src/constants/actions';
1616
import { resolved, rejected } from '../src/constants/requestStatus';
17+
import { lastCacheUpdate as lastCacheUpdateSymbol } from '../src/symbols';
1718

1819
const createCallAPIActionFrom = ({
1920
meta: { name },
@@ -33,10 +34,10 @@ const successfulQueryBySlugState = initialReducerState.set(
3334
new Immutable.List([
3435
{
3536
...successfulQueryBySlug.payload.response[0]._embedded.author[0],
36-
lastCacheUpdate: Date.now() },
37+
[lastCacheUpdateSymbol]: Date.now() },
3738
{
3839
...successfulQueryBySlug.payload.response[0],
39-
lastCacheUpdate: Date.now(),
40+
[lastCacheUpdateSymbol]: Date.now(),
4041
_embedded: { author: 0 },
4142
},
4243
])

test/selector.spec.js

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
import { describe, it } from 'mocha';
22
import expect from 'expect';
33
import Immutable from 'immutable';
4+
import { createSelector } from 'reselect';
5+
46
import { initialReducerState } from '../src/ReduxWPAPI';
5-
import { selectQuery } from '../src/selectors';
7+
import { selectQuery, withDenormalize } from '../src/selectors';
68
import { pending, resolved } from '../src/constants/requestStatus';
79

8-
describe('Selector', () => {
10+
describe('Selector selectQuery', () => {
911
it('should return a Request for empty state', () => {
1012
const state = { wp: initialReducerState };
1113
expect(selectQuery('test')(state))
@@ -141,3 +143,31 @@ describe('Selector', () => {
141143
});
142144
});
143145

146+
147+
describe('withDenormalize', () => {
148+
it('should allow consumers to denormalize resources by local ids within selector', () => {
149+
const resources = [
150+
{ id: 1,
151+
title: 'lol',
152+
_links: { parent: { url: 'http://dumb.com/test/2' } },
153+
_embedded: { parent: 1 },
154+
},
155+
{ id: 2, title: 'lol 2' },
156+
];
157+
const storeState = {
158+
wp: (
159+
initialReducerState
160+
.setIn(['resources'], new Immutable.List(resources))
161+
),
162+
};
163+
164+
const selector = withDenormalize(
165+
createSelector(
166+
() => 1,
167+
id => denormalize => denormalize(id)
168+
)
169+
);
170+
const selectedState = selector(storeState);
171+
expect(selectedState).toInclude(resources[1]);
172+
});
173+
});

0 commit comments

Comments
 (0)