@simonsmith/redux-api-middleware v1.0.0
Redux API middleware
A tiny Redux middleware for simplifying the communication with API endpoints.
Why
When writing async action creators that interact with API endpoints (for
example with redux-thunk) it is not uncommon for logic to be duplicated.
Each creator will typically configure a request via a library such as axios or
fetch and dispatch additional actions for loading or error states.
By using middleware these actions can be intercepted and this logic can be centralised, greatly reducing boilerplate and easing testing.
Installation
yarn
yarn add @simonsmith/redux-api-middlewarenpm
npm install --save @simonsmith/redux-api-middlewareQuick example
Pass createApiMiddleware a request library as the first argument and any
configuration options. Any promise based library can be used such as
fetch
or ky. You're free to pass your own
function to prepare the promise for the
middleware (such as first calling response.json() in fetch).
import {createApiMiddleware} from '@simonsmith/redux-api-middleware';
import {configureStore} from 'redux-starter-kit';
import axios from 'axios';
const store = configureStore({
reducer: state => state,
middleware: [
createApiMiddleware(axios, {
requestDefaults: {
baseURL: 'http://myapi.com',
},
}),
],
});Use the apiRequest helper function to send a request through the middleware.
Actions for request start, end and failure will be dispatched as well as calling
your onSuccess or onFailure callbacks. These can be used to dispatch
additional actions.
import {apiRequest} from '@simonsmith/redux-api-middleware';
// An action creator
export function requestAllUsers() {
return apiRequest('/users', {
type: FETCH_USERS,
onSuccess: dispatchAnotherAction,
});
}API
createApiMiddleware(request, options?)
Returns a function that acts as the middleware which can be passed to Redux during store creation.
request: (url, options?) => Promise
This function is used by the middleware to send a network request. It must return a promise that resolves with the payload from the server.
For example when using the fetch function it requires response.json() to be
called and the promise returned:
const requestFunc = (url, options) => {
return fetch(url, options).then(res => res.json());
}
createApiMiddleware(requestFunc),options?: Object
- actionTypes (object)
- start (string) - Dispatched before a request begins default
API_REQUEST_START - end (string) - Dispatched when a request ends default
API_REQUEST_END - failure (string) - Dispatched when a request fails default
API_REQUEST_FAILURE
- start (string) - Dispatched before a request begins default
- requestDefaults (object) Options passed to the request library on each request
The options argument passed to request is merged with the values from the
apiRequest function and any values in the requestDefaults object provided in
the options. This allows things like headers to be provided on all requests by
default:
createApiMiddleware(requestFunc, {
requestDefaults: {
headers: {
'Content-Type': 'application/json',
},
},
}),These can be overridden in the second argument to apiRequest if needed.
apiRequest(url, options?)
Creates an action that will be dispatched to the store and intercepted by the middleware.
function updatePost(id, newPost) {
return apiRequest(`/post/${id}`, {
type: 'UPDATE_POST',
onSuccess: getPosts,
onFailure: logError,
// `method` and `data` passed to the `request` function
method: 'PUT',
data: newPost,
});
}url: String
The url that the request will be made to.
options?: Object
Can contain a type, onSuccess and onFailure. Any additional values will be
merged with the requestDefaults passed to the request function.
type: String
When a type is provided the middleware will dispatch each of the actions in
actionTypes with a payload of the type value. This allows different
requests to be differentiated from one another.
onSuccess: Function
When provided this function will be called with the response from the request
function. Its return value should be an action as it will be passed to dispatch.
onFailure: Function
When provided this function will be called with the error from the request
function. Its return value should be an action as it will be passed to dispatch.
Configuring common request libraries
axios
One of the simplest to configure, the axios object can be passed directly to
createApiMiddleware:
createApiMiddleware(axios);ky
Return a promise from the json method:
const requestFunc = (url, options) => ky(url, options).json();
createApiMiddleware(requestFunc);fetch
Return a promise from response.json:
const requestFunc = (url, options) => fetch(url, options).then(res => res.json());
createApiMiddleware(requestFunc);Actions
In addition to the success and failure callbacks there are also actions dispatched when a request is loading or encounters an error. This allows the logic to be centralised in separate reducers.
All actions dispatched conform to the FSA spec.
Loading
When the type option is used the following loading actions are dispatched:
apiRequest('/url', {
type: 'SOME_ACTION',
});
// actions
{type: 'API_REQUEST_START', payload: 'SOME_ACTION'}
{type: 'API_REQUEST_END', payload: 'SOME_ACTION'}The payload is set to the type value allowing these actions to be
differentiated in a reducer.
Error
If an error is encountered an error action is dispatched:
{type: 'API_REQUEST_FAILURE', payload: Error('some error'), error: true}Handling loading states in a reducer
It's recommended to create a separate reducer to handle loading actions. This
has the added benefit of keeping reducers free from repetitive logic (such as
isLoading state):
import {createReducer} from 'redux-starter-kit';
const initialState = {};
export const loadingReducer = createReducer(initialState, {
API_REQUEST_START: (state, action) => {
return {
...state,
[action.payload]: true,
};
},
API_REQUEST_END: (state, action) => {
return {
...state,
[action.payload]: false,
};
},
});// state example
{
loading: {
FETCH_PROFILE: true,
CREATE_USER: false,
}
}Components can select the loading state they are interested in and use this value to display a spinner or text to the user.
Contributing
Pull requests are welcome!
Credit
6 years ago