Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
4eda4f7
Updates and fixes the README.md
edygar Sep 12, 2016
8e63e29
Middleware tests on the way
edygar Sep 13, 2016
94b9a4b
[WIP] Middlewares are now been tested
edygar Sep 13, 2016
ea8dbe4
Fixing selector
edygar Sep 14, 2016
76ecf39
Adds Middleware and Selector tests
edygar Sep 14, 2016
4b4033f
Merge branch 'master' of github.com:log-oscon/redux-wpapi into featur…
edygar Sep 14, 2016
3edee49
Test Middleware return; Denormalize in selectors operation unwise
edygar Sep 15, 2016
b50699e
Document patch release
edygar Sep 15, 2016
140e725
Conforms Changelog with guidelines
edygar Sep 16, 2016
255c97e
Symbols introduced
edygar Sep 16, 2016
00847c6
Symbols introduced
edygar Sep 16, 2016
86a4e7a
withSelector on its way
edygar Sep 16, 2016
81744cc
withDenormalize now provides `denormalize` func for a selector
edygar Sep 17, 2016
0c35546
Fix typos
edygar Sep 17, 2016
f29f1ee
Merge branch 'feature/test' into feature/public-denormalize
edygar Sep 17, 2016
2e4f386
localID -> resourceLocalID (conformant with adapter signature)
edygar Sep 17, 2016
a716e0b
Conforms better with adapter api
edygar Sep 17, 2016
a04ab94
Move symbols to root
edygar Sep 18, 2016
b07cb92
Changelog
edygar Sep 18, 2016
432923e
Regressing version (the version was mistakenly changed)
edygar Sep 18, 2016
b5fc0fd
Merge branch 'feature/test' into feature/public-denormalize
edygar Sep 18, 2016
b5b6056
Conforms with linter
edygar Sep 18, 2016
6fae2e0
Exports Symbols at root
edygar Sep 19, 2016
e0abc57
Exposes withDenormalize
edygar Sep 19, 2016
30a01da
Denormalize returns null when nothing is found
edygar Sep 19, 2016
d6ba9fd
Fix alignments, adds CHANGELOG PR reference
edygar Sep 19, 2016
7ba98a4
Merge branch 'feature/test' into feature/public-denormalize
edygar Sep 19, 2016
1b59f20
Add PR reference
edygar Sep 19, 2016
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
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,11 @@ Some guidelines in reading this document:
* Being that these are the early days of the repository, we have some code changes that were added directly and without much detail, for these we have a link to the commit instead of the PR.
* Annotations starting with **[BC]** indicates breaking change.

## [new release]

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

## 1.0.1

* Fix selector, which was referring to `entity` instead `resource`.
Expand Down
22 changes: 15 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
A [node-wpapi](https://github.com/WP-API/node-wpapi) integration for a Redux based Application.

## How it Works
This library exposes [node-wpapi](https://github.com/WP-API/node-wpapi) instance through the actionCreator [wp](#wp-Action-Creator). The resulting
This library exposes [node-wpapi](https://github.com/WP-API/node-wpapi) instance through the actionCreator [callAPI](#/src/actions/callAPI.js). The resulting
action is interpreted in [the middleware](#the-middleware), doing so by resolving the request and controlling the reducer through actions.

## Installation
Expand Down Expand Up @@ -35,18 +35,26 @@ import { wp, selectQuery, ResponseStatus } from 'redux-wpapi';
import { connect } from 'react-redux';

export class HomePage extends React.Component {
componentWillMount() {
this.props.wp(
static loadData(props) {
return props.callAPI(
// The name where the request state will be placed
'HomePagePosts',
// A callback where wpapi instance is injected
api =>
api.posts()
.page(this.props.page)
.perPage(this.props.perPage)
api.posts()
.page(props.page)
.perPage(props.perPage)
);
}

componentWillMount() {
HomePage.loadData(this.props);
}

componentWillReceiveProps(props) {
HomePage.loadData(props);
}

render() {
const { status, data: posts } = this.props.request;

Expand All @@ -67,7 +75,7 @@ export class HomePage extends React.Component {

export default connect({
request: selectQuery('HomePagePosts'),
}, { wp })(HomePage);
}, { callAPI })(HomePage);
```

## Contributions
Expand Down
104 changes: 58 additions & 46 deletions src/ReduxWPAPI.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import isArray from 'lodash/isArray';
import isUndefined from 'lodash/isUndefined';
import { selectQuery } from './selectors';
import WPAPIAdapter from './adapters/wpapi';
import { lastCacheUpdate as lastCacheUpdateSymbol } from './symbols';

import {
REDUX_WP_API_CALL,
Expand All @@ -25,7 +26,7 @@ import {
rejected,
} from './constants/requestStatus';

const initialReducerState = Immutable.fromJS({
export const initialReducerState = Immutable.fromJS({
requestsByName: {},
requestsByQuery: {},

Expand Down Expand Up @@ -66,7 +67,6 @@ export default class ReduxWPAPI {
name: action.payload.name,
aggregator: this.adapter.getAggregator(this.adapter.getUrl(request)),
operation: this.adapter.getOperation(request),
params: action.payload.params,
requestAt: Date.now(),
};

Expand All @@ -77,24 +77,23 @@ export default class ReduxWPAPI {
let data;
const state = store.getState().wp;
const indexes = this.adapter.getIndexes(request);
const localID = this.getResourceLocalID(state, meta.aggregator, indexes);
const resourceLocalID = this.getResourceLocalID(state, meta.aggregator, indexes);

payload.cacheID = this.adapter.generateCacheID(request);
payload.page = parseInt(this.adapter.getRequestedPage(request) || 1, 10);

if (localID) {
cache = state.getIn(['resources', localID]);
data = [localID];
if (resourceLocalID) {
cache = state.getIn(['resources', resourceLocalID]);
data = [resourceLocalID];
}

if (cache) {
lastCacheUpdate = cache.lastCacheUpdate;
lastCacheUpdate = cache[lastCacheUpdateSymbol];
} else {
cache = state.getIn(['requestsByQuery', payload.cacheID, payload.page]);
data = state.get('data');
}

if (cache && (localID || (isUndefined(localID) && !cache.get('error')))) {
if (cache && (resourceLocalID || (isUndefined(resourceLocalID) && !cache.get('error')))) {
lastCacheUpdate = lastCacheUpdate || cache.get('responseAt') || cache.get('requestAt');
next({
meta,
Expand All @@ -107,14 +106,18 @@ export default class ReduxWPAPI {
},
});

let ttl = this.adapter.getTTL(request);
let ttl;
if (this.adapter.getTTL) {
ttl = this.adapter.getTTL(request);
}

if (ttl !== 0 && !ttl) {
ttl = this.settings.ttl;
}

if (Date.now() - lastCacheUpdate < ttl) {
return Promise.resolve(
store.getState().wp.getIn(['requestsByName', meta.name])
selectQuery(meta.name)(store.getState())
);
}
}
Expand All @@ -126,23 +129,25 @@ export default class ReduxWPAPI {
meta,
});

return this.adapter.sendRequest(request)
.then(
response =>
next({
type: REDUX_WP_API_SUCCESS,
payload: { ...payload, response },
meta: { ...meta, responseAt: Date.now() },
}),
error =>
next({
type: REDUX_WP_API_FAILURE,
payload,
error,
meta: { ...meta, responseAt: Date.now() },
})
)
.then(() => selectQuery(meta.name)(store.getState()));
return (
this.adapter.sendRequest(request)
.then(
response =>
next({
type: REDUX_WP_API_SUCCESS,
payload: { ...payload, response },
meta: { ...meta, responseAt: Date.now() },
}),
error =>
next({
type: REDUX_WP_API_FAILURE,
payload,
error,
meta: { ...meta, responseAt: Date.now() },
})
)
.then(() => selectQuery(meta.name)(store.getState()))
);
}

reducer = (state = initialReducerState, action) => {
Expand Down Expand Up @@ -217,10 +222,10 @@ export default class ReduxWPAPI {
}

const data = [];
const aditionalData = { lastCacheUpdate: requestState.responseAt };
const additionalData = { lastCacheUpdate: requestState.responseAt };

body.forEach(resource => {
newState = this.indexResource(newState, aggregator, resource, aditionalData);
newState = this.indexResource(newState, aggregator, resource, additionalData);
data.push(this.getResourceLocalID(newState, aggregator, resource));
});

Expand Down Expand Up @@ -281,10 +286,10 @@ export default class ReduxWPAPI {
const _links = this.resolveAliases(resource._links, curies) || {};
delete _links.curies;

let localID = this.getResourceLocalID(state, aggregator, resource);
let resourceLocalID = this.getResourceLocalID(state, aggregator, resource);
let oldState = {};
if (!isUndefined(localID)) {
oldState = newState.getIn(['resources', localID]);
if (!isUndefined(resourceLocalID)) {
oldState = newState.getIn(['resources', resourceLocalID]);
}

if (resource._embedded) {
Expand Down Expand Up @@ -318,27 +323,34 @@ export default class ReduxWPAPI {
});
}

if (isUndefined(localID)) {
localID = newState.get('resources').size;
if (isUndefined(resourceLocalID)) {
resourceLocalID = newState.get('resources').size;
}

newState = newState.setIn(
['resources', localID],
this.settings.transformResource(this.adapter.transformResource({
...oldState,
...resource,
_links,
_embedded,
lastCacheUpdate: meta.lastCacheUpdate,
}))
);
let resourceTransformed = {
...oldState,
...resource,
...meta,
_links,
_embedded,
};

if (this.adapter.transformResource) {
resourceTransformed = this.adapter.transformResource(resourceTransformed);
}

if (this.settings.transformResource) {
resourceTransformed = this.settings.transformResource(resourceTransformed);
}

newState = newState.setIn(['resources', resourceLocalID], resourceTransformed);
const indexers = this.settings.customCacheIndexes[aggregator];

forEach(isArray(indexers) ? ['id'].concat(indexers) : ['id', indexers], indexer => {
if (!isUndefined(resource[indexer])) {
newState = newState.setIn(
['resourcesIndexes', aggregator, indexer, resource[indexer]],
localID
resourceLocalID
);
}
});
Expand Down
4 changes: 2 additions & 2 deletions src/actions/callAPI.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { REDUX_WP_API_CALL } from '../constants/actions';

export default (name, request, aditionalParams = {}) => ({
export default (name, request, additionalParams = {}) => ({
type: REDUX_WP_API_CALL,
payload: { name, request, aditionalParams },
payload: { name, request, additionalParams },
});
16 changes: 8 additions & 8 deletions src/adapters/wpapi.js
Original file line number Diff line number Diff line change
Expand Up @@ -269,16 +269,16 @@ export default class WPAPIAdapter {
* fetched. The request must carry at least operation (get|create|update\delete) and required data
* in order to call API later at `callAPI`.
*
* @param {Object} payload The action payload
* @param {Object} payload.request The lib consumer input for calling the api
* @param {Object} payload.aditionalParams Aditional params in order to make request, generally
* meta data such as method or header to be handled by
* `callAPI`
* @return {Object} The Request Object
* @param {Object} payload The action payload
* @param {Object} payload.request The lib consumer input for calling the api
* @param {Object} payload.additionalParams additional params in order to make request, generally
* meta data such as method or header to be handled by
* `callAPI`
* @return {Object} The Request Object
*/
buildRequest({ request: requestBuilder, aditionalParams }) {
buildRequest({ request: requestBuilder, additionalParams }) {
const wpRequest = requestBuilder(this.api);
const { operation = 'get', ...body } = aditionalParams;
const { operation = 'get', ...body } = additionalParams;

return { wpRequest, operation, body };
}
Expand Down
6 changes: 2 additions & 4 deletions src/callAPI.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import { REDUX_WP_API_CALL } from './constants/actions';

export * as types from './constants/actions';

export default (name, request, aditionalParams = {}) => ({
export default (name, request, additionalParams = {}) => ({
type: REDUX_WP_API_CALL,
payload: { name, request, aditionalParams },
payload: { name, request, additionalParams },
});
3 changes: 2 additions & 1 deletion src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@ import ReduxWPAPI from './ReduxWPAPI';
export default ReduxWPAPI;
export * as RequestStatus from './constants/requestStatus';
export callAPI from './actions/callAPI';
export { selectQuery } from './selectors';
export * as Symbols from './symbols';
export { selectQuery, withDenormalize } from './selectors';
31 changes: 25 additions & 6 deletions src/selectors.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,21 @@
import isFunction from 'lodash/isFunction';
import { createSelector } from 'reselect';

import { pending } from './constants/requestStatus';
import { mapDeep } from './helpers';
import { id as idSymbol } from './symbols';

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

const resource = resources.get(id);
if (!resource) {
return null;
}

memoized[id] = {
[idSymbol]: id,
...resource,
...mapDeep(resource._embedded || {},
embeddedId => denormalize(resources, embeddedId, memoized)
Expand All @@ -17,7 +25,21 @@ export const denormalize = (resources, id, memoized = {}) => {
return memoized[id];
};

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

export const withDenormalize = thunk =>
createSelector(
localResources,
thunk,
(resources, target) => {
if (!isFunction(target)) {
return target;
}

const memo = {};
return target(id => denormalize(resources, id, memo));
}
);

export const selectQuery = name => createSelector(
createSelector(
Expand All @@ -38,7 +60,7 @@ export const selectQuery = name => createSelector(
return currentRequest;
}
),
localresources,
localResources,
(request, resources) => {
if (!request) {
return {
Expand All @@ -63,10 +85,7 @@ export const selectQuery = name => createSelector(
}

return request.set(
'data',
request.get('operation') === 'get' ?
data.map(id => denormalize(resources, id, memo)) :
data
'data', data.map(id => denormalize(resources, id, memo))
).toJSON();
}
);
2 changes: 2 additions & 0 deletions src/symbols.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export const id = Symbol('ResourceLocalID');
export const lastCacheUpdate = Symbol('lastCacheUpdate');
13 changes: 13 additions & 0 deletions test/adapter.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { describe, it } from 'mocha';
import expect from 'expect';
import WPAPIAdapter from '../src/adapters/wpapi';

describe('WPAPI Adapter', () => {
it('should complain if no api is given', () => {
expect(() => {
// eslint-disable-next-line
new WPAPIAdapter();
}).toThrow();
});
});

Loading