Skip to content

Commit

Permalink
Advanced middleware feature (#23)
Browse files Browse the repository at this point in the history
* Add advanced middleware feature to inject custom network adapters

This moves most of the logic from the query middleware to the new
advanced middleware. The query middleware will call advanced using
the default superagent adapter.

* Add tests for the superagent adapter

* Add advanced CommonJS entry point alias

* Create separate default and advanced entries

* Fix advanced invalid imports

* Add test for PATCH in superagent adapter

* Update README with docs about redux-query/advanced
  • Loading branch information
iamlacroix authored and ryanashcraft committed Mar 19, 2017
1 parent 908a9b2 commit d922161
Show file tree
Hide file tree
Showing 8 changed files with 452 additions and 339 deletions.
47 changes: 47 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,53 @@ The result of the promise returned by `mutateAsync` will be the following object

Similarly to how mutations are triggered by dispatching `mutateAsync` actions, you can trigger requests by dispatching `requestAsync` actions with a request query config.

### Usage without superagent with `redux-query/advanced`

By default, `redux-query` makes XHR requests using the [superagent](https://github.com/visionmedia/superagent) library. If you'd rather use a different library for making requests, you can use the `redux-query`'s "advanced" mode by importing from `redux-query/advanced` instead of `redux-query`.

Note: The default [`queryMiddleware`](./src/middleware/query.js) exported from the main `redux-query` entry point is simply a [superagent adapter](./src/adapters/superagent.js) bound to `queryMiddlewareAdvanced`.

Example `queryMiddlewareAdvanced` usage:

```javascript
import { applyMiddleware, createStore, combineReducers } from 'redux';
import { entitiesReducer, queriesReducer, queryMiddlewareAdvanced } from 'redux-query/advanced';

// A function that takes a url, method, and other options. This function should return an object
// with two required properties: execute and abort.
import myNetworkAdapter from './network-adapter';

export const getQueries = (state) => state.queries;
export const getEntities = (state) => state.entities;

const reducer = combineReducers({
entities: entitiesReducer,
queries: queriesReducer,
});

const store = createStore(
reducer,
applyMiddleware(queryMiddlewareAdvanced(myNetworkAdapter)(getQueries, getEntities))
);
```

#### Network adapters

You must provide a function to `queryMiddlewareAdvanced` that adheres to the following `NetworkAdapter` interface:

```javascript
type NetworkAdapter = (
url: string,
method: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE',
config?: { body?: string | Object, headers?: Object, credentials?: 'omit' | 'include' } = {},
) => Adapter;

type NetworkRequest = {
execute: (callback: (err: any, resStatus: number, resBody: ?Object, resText: string) => void) => void,
abort: () => void,
};
```

## Example

A fork of the `redux` [Async](https://github.com/reactjs/redux/tree/master/examples/async) example is included. To run, first build the package:
Expand Down
1 change: 1 addition & 0 deletions advanced.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module.exports = require('./dist/commonjs/advanced');
49 changes: 49 additions & 0 deletions src/adapters/superagent.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import superagent from 'superagent';
import * as httpMethods from '../constants/http-methods';

export const createRequest = (url, method) => {
switch (method) {
case httpMethods.GET:
return superagent.get(url);
case httpMethods.POST:
return superagent.post(url);
case httpMethods.PUT:
return superagent.put(url);
case httpMethods.PATCH:
return superagent.patch(url);
case httpMethods.DELETE:
return superagent.del(url);
default:
throw new Error(`Unsupported HTTP method: ${method}`);
}
};

const superagentNetworkAdapter = (url, method, { body, headers, credentials } = {}) => {
const request = createRequest(url, method);

if (body) {
request.send(body);
}

if (headers) {
request.set(headers);
}

if (credentials === 'include') {
request.withCredentials();
}

const execute = (cb) => request.end((err, response) => {
const resStatus = (response && response.status) || 0;
const resBody = (response && response.body) || undefined;
const resText = (response && response.text) || undefined;

cb(err, resStatus, resBody, resText);
});

const abort = () => request.abort();

return { execute, abort };
};

export default superagentNetworkAdapter;
12 changes: 12 additions & 0 deletions src/advanced.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import * as actions from './actions';
import * as actionTypes from './constants/action-types';
import * as httpMethods from './constants/http-methods';
import * as querySelectors from './selectors/query';

export { default as connectRequest } from './components/connect-request';
export { getQueryKey, reconcileQueryKey } from './lib/query-key';
export { default as queriesReducer } from './reducers/queries';
export { default as entitiesReducer } from './reducers/entities';
export { default as queryMiddlewareAdvanced } from './middleware/query-advanced';
export { cancelQuery, mutateAsync, requestAsync, removeEntities, removeEntity } from './actions';
export { actions, actionTypes, httpMethods, querySelectors };
Loading

0 comments on commit d922161

Please sign in to comment.