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-middleware
through 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
endpointand
optionsfields 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
checkStatus
function (see create middleware). If succeed it will try to parse response withparseResponse
function (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
function
action type in[CALL_API].types
and 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
promise
as 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