Redux API Middleware to make API calls in generic and declarative way. Allows to fire single or multiple API calls at once or sequentially, without dependency on fetch implementation.
- Example
- Usage and Docs
- Installation
- Create middleware
- Action creator
- Lifecycle
- Dispatched FSAs
- FAQ
- Acknowledgements
- License
This is the simplest form, nothing wrong here, but take a look at create middleware and action creator sections if you need customization, its quite flexible.
// actions file
import { CALL_API } from `redux-callapi-middleware`;
export const callApi = () => ({
[CALL_API]: {
type: ACTION_TYPE,
endpoint: 'http://yourdomain.com/api/posts',
options: {
method: 'GET'
}
}
});
// somewhere
import { callApi } from 'actions';
dispatch(callApi());This will dispatch request FSA before request:
{ type: ACTION_TYPE, [CALL_API_PHASE]: REQUEST }If everything is fine it will dispatch success FSA on request response:
{
type: ACTION_TYPE,
payload: payload,
[CALL_API_PHASE]: SUCCESS
}Otherwise it will dispatch failure FSA:
{
type: ACTION_TYPE,
payload: error,
[CALL_API_PHASE]: FAILURE
error: true
}// actions file
import { CALL_API } from `redux-callapi-middleware`;
export const callApi = () => ({
[CALL_API]: {
types: ['REQUEST', 'SUCCESS', 'FAILURE'],
endpoint: 'http://yourdomain.com/api/posts',
options: {
method: 'GET'
}
}
});
// somewhere
import { callApi } from 'actions';
dispatch(callApi());This will dispatch request FSA before request:
{ type: 'REQUEST' }If everything is fine it will dispatch success FSA on request response:
{
type: 'SUCCESS',
payload: payload
}Otherwise it will dispatch failure FSA:
{
type: 'FAILURE',
payload: error,
error: true
}- Install
redux-callapi-middlewarethrough npm:
$ npm install redux-callapi-middleware --save- Add middleware with redux
applyMiddleware():
import { createStore, applyMiddleware } from 'redux';
import apiMiddleware from 'redux-callapi-middleware';
import reducers from './reducers';
const store = createStore(
reducers,
applyMiddleware(apiMiddleware)
);Middleware exposes createMiddleware function which accepts object with callApi function.
So you can pass any fetch implementation you wish with any response interceptors.
import { createMiddleware } from 'redux-callapi-middleware';
import fetch from 'isomorphic-fetch';
const apiMiddleware = createMiddleware({ callApi: fetch });Or with interceptors
import { createMiddleware } from 'redux-callapi-middleware';
import fetch from 'isomorphic-fetch';
const onSuccess = (response) => {
if (!response.ok) {
throw new Error('Error');
}
return response;
}
const callApi = (url, options) => fetch(url, options).then(onSuccess);
const apiMiddleware = createMiddleware({ callApi });Action creator should return an object with [CALL_API] property with batch, endpoint, options and types fields. See example.
An API endpoints to parallel API call. Array of Objects contains endpoint and options fields in same format as [CALL_API].endpoint and [CALL_API].options.
batch: [
{ endpoint1, options1 },
{ endpoint2, options2 },
],An API endpoints to sequnced API calls. Array of Functions with all the previous responses, should return Object with batch or endpoint and options fields. contains endpointandoptionsfields in same format as[CALL_API].endpointand[CALL_API].options`.
endpoint1,
options1
queue: [
(action, getState, responses) => ({ endpoint2, options2 }),
(action, getState, responses) => ({
batch: [
{ endpoint3, options3 },
{ endpoint4, options4 },
],
}),
],The first queued item will be called with responses from endpoint1 and fire one request, second will be called with both responses from endpoint1 and endpoint2 and fire two requests in parallel. And in result if everything fine SUCCESS action will be fired with all the 4 responses from all the 4 requests.
An API endpoint to call. Used if batch is not populated. String or function which receives state and returns string.
endpoint: 'someurl',// calculate url from state
endpoint: (apiAction, state) => 'someurl',Request options object. Used if batch is not populated. Object or function which receives state and returns object.
It uses isomorphic-fetch under the hood, so any valid options for fetch, like body, credentials, headers and etc.
options: { 'method': 'PUT'},// calculate options from state
options: (apiAction, state) => { 'method': 'PUT'},Array of actions to dispatch as middleware output. It might be strings or symbols or FSA's or functions which should return FSA's or mix of them.
So its fine to have following structure in [CALL_API].types:
[
(action, state) => ({
type: 'REQUEST',
payload: { isFetching: true }
}),
{ type: 'SUCCESS' },
'FAILURE'
]Action type to dispatch as middleware output. It will be the same type for REQUEST, SUCCESS and FAILURE actions, but phase of action will be attached to an action under special [CALL_API_PHASE] property (all the info in 0.5.0 release notes), i.e.:
{
type: ACTION_TYPE,
[CALL_API_PHASE]: REQUEST || SUCCESS || FAILURE
}- Checks if action has
[CALL_API]. If no it stops and dispatches action to next middleware. - Builds request endpoint and options. There might be error handling in future.
- Dispatches to next middleware request FSA from first item of
[CALL_API].types. - Performs API call by request params.
- Checks response status with
checkStatusfunction (see create middleware). If succeed it will try to parse response withparseResponsefunction (see create middleware) and will dispatch success FSA from second item of[CALL_API].types. Otherwise, it will dispatch failure FSA from third item of[CALL_API].types.
The [CALL_API].types array can hold 4 kind of actions types:
strings- will be converted to FSAobject.symbols- same as strings.object- it should be valid FSAobject.{ type: 'REQUEST', payload: { page: 5 } }
function- most flexible way it will receive 3 arguments:[CALL_API]object, state and payload. But it should return valid FSAobject.(apiAction, state, payload) => ({ type: 'SUCCESS', payload })
Not receives payload as FSA property or function argument from middleware. (There is no payload at this moment)
Receives response as payload, it will be converted to json or text by middleware.
Receives error as payload, response attached to error.response property. FSA also have error flag set to true.
- Usage with thunk (dont forget to put api middleware after thunk in middleware chain):
import { CALL_API } from `redux-callapi-middleware`;
const callApi = () => (
(dispatch, getState) =>
// do anything you need here
return dispatch({
[CALL_API]: {
types: ['REQUEST', 'SUCCESS', 'FAILURE'],
endpoint: 'http://yourdomain.com/api/posts',
options: {
method: 'GET'
}
}
})
)- Need a meta property in FSA?
{
[CALL_API]: {
types: [{
type: 'REQUEST',
meta: 'anything'
},
(apiAction, state, payload) => (
{
type: 'SUCCESS',
payload,
meta: payload.meta
}
), {
type: 'FAILURE',
meta: 'anything'
}],
endpoint: 'http://yourdomain.com/api/posts',
options: {
method: 'GET'
}
}
}- Need a payload function? Use
functionaction type in[CALL_API].typesand build own FSA.
{
[CALL_API]: {
types: [
'REQUEST',
'SUCCESS',
// lets pass failure type as function
(apiAction, state, error) => {
// do anything you need but return FSA object
const payload = formatErrorPayload(error)
return {
type: 'FAILURE',
meta: 'anything',
payload
};
}],
endpoint: 'http://yourdomain.com/api/posts',
options: {
method: 'GET'
}
}
}- Need a
promiseas output action?
Not supported, but might work with redux-promise.
-
Difference with redux-api-middleware?
-
It dispatches errors only with error type
-
It not dispatches "programmatic" errors, like errors on endpoint generation.
-
It gives more control with functions as actions types
-
Not supports promises, but take look to redux-promise.
-
Allows to batch API calls
-
Want to have base URL?
Write a wrapper around your callApi action creator.
- Want to check custom headers or have custom parse response?
- Wish to have custom error handling?
Originally inspired and extracted from Dan Abramov the real-world sample in the redux repository. Thanks to all developers and contributors.
The MIT License (MIT) Copyright (c) 2017 Artur Charaev