redux-fetch-requests
v1.0.0
Published
Redux middleware to simplify handling of AJAX requests
Downloads
4
Readme
- redux-fetch-requests
- Motivation
- process API-specific redux actions (it's an action with the request object)
- provides an interceptors for request/response processing
- Reducer helper
- process defined as well as correspond success, error and cancel actions
- keeps the pending field in accordance with request/(success|error|cancel) counter
- allows to work with arrays
- allows to define default data object via getDefaultData option
- allows to define result data processing via getData option
- allows to define error data processing via getError option
- allows to define action(s) to reset store to initial state (this action can be the same as Request)
- License
redux-fetch-requests
Redux middleware to simplify handling of AJAX requests
Motivation
Using the redux-fetch-requests it's possible to get rid of plumber code and concentrate on API call results proceccing in case when you use the redux flow.
The redux-fetch-requests library is inspired by great redux-saga-requests, but does not require the redux-saga to use and is much smaller and a bit simplier in usage. Moreover, the redux-fetch-requests works only with browser fetch API.
The redux-fetch-requests middleware takes responsibility for:
process API-specific redux actions (it's an action with the request object)
attach the middleware to store
import { createStore, applyMiddleware } from 'redux';
import { createMiddleware } from 'redux-fetch-requests';
const fetchRequestsOptions = {
// fetchInstance = fetch,
baseUrl: '/api', // baseUrl = ''
// abortController = new AbortController()
// cancelOn = [FETCH_CANCEL_REQUESTS],
// onRequest = undefined,
// onSuccess = undefined,
// onError = undefined,
// onCancel = undefined,
};
const store = createStore(
rootReducer,
applyMiddleware(
createMiddleware(options),
),
);
process actions with request section
const GET_BOOKS = 'GET_BOOKS';
const getBooks() {
return {
type: GET_BOOKS,
request: {
url: '/books',
}
}
}
will produce call like
GET http://localhost/api/books
add query params
const GET_BOOKS = 'GET_BOOKS';
const getBooks(name) {
return {
type: GET_BOOK,
request: {
url: '/books',
query: {
name,
}
}
}
}
GET http://localhost/api/books?name=asdf
specify the HTTP method
const DELETE_BOOK = 'DELETE_BOOK';
const deleteBook(id) {
return {
type: DELETE_BOOK,
request: {
url: `/books/${id}`,
method: 'DELETE',
}
meta: {
id,
}
}
}
DELETE http://localhost/api/books/10
const ADD_BOOK = 'ADD_BOOK';
const addBook(title, description, ISBN) {
return {
type: ADD_BOOK,
request: {
url: `/books`,
method: 'POST',
body: JSON.stringify({
title,
description,
ISBN,
})
}
}
}
POST http://localhost/api/books/10
{"title":"title","description":"description","ISBN":"2-266-11156-6"}
process fetch success flow
- decode required response type (json, blob, formData, text, arrayBuffer)
- fire correspond redux action (<ACTION_TYPE>_SUCCESS) with decoded data
- pass the meta section from initial API action to the success action
const DELETE_BOOK = 'DELETE_BOOK';
const deleteBook(id) {
return {
type: DELETE_BOOK,
request: {
url: `/books/${id}`,
method: 'DELETE',
}
meta: {
id,
}
}
}
success action:
{
type: 'DELETE_BOOK_SUCCESS',
response: {
data: 'some data'
status: 200,
statusMessage: 'OK'
},
data: 'some data',
meta: {
id: 1,
},
}
process fetch error flow
- generate an error when HTTP result code is not 200..299
- try to decode response data via json() helper
- fire correspond redux action (<ACTION_TYPE>_ERROR) with decoded data
- pass the meta section from initial API action to the error action
error action:
{
type: 'DELETE_BOOK_ERROR',
error: {
data: 'something went wrong'
status: 500,
statusMessage: 'internal error'
},
meta: {
id: 1,
},
}
process fetch abort flow
- instantiate AbortController in the middleware to proceed request cancelling
- pass signal to the fetch call (is controlled by isCancelling request flag)
- watch cancelling action (can be defined as an action types array or via a function)
- on cancelling action call AbortController abort() which leads cancelling all active fetch requests
- fire correspond redux action (<ACTION_TYPE>_CANCEL) with error definition
cancel action:
{
type: 'DELETE_BOOK_CANCEL',
error: {
name: 'AbortError',
message: 'request aborted',
},
meta: {
id: 1,
},
}
provides an interceptors for request/response processing
onRequest
- it's possible to modify the Request before call
- for example, to add some auth headers
const reduxFetchOptions = {
onRequest: (request, action, dispatch, getState, options) => {
return {
...request,
headers: {
Authorization: 'Basic YWxhZGRpbjpvcGVuc2VzYW1l',
}
}
}
}
onSuccess
* it's possible to provide some common response data processing if required
* it's possible to fire a **redux** action if required
const reduxFetchOptions = {
onSuccess: (response, action, dispatch, getState, options) => {
// do sth with the response, dispatch some action etc
return response;
}
}
onError
* it's possible to fire a **redux** action if required
const reduxFetchOptions = {
onError: (error, action, dispatch, getState, options) => {
// do sth here like dispatch some action
if (error.status === 404) {
dispatch(popupAction('no access'));
}
}
}
onCancel
* it's possible to fire a **redux** action if required
const reduxFetchOptions = {
onCancel: (error, action, dispatch, getState, options) => {
// do sth here like dispatch some action
}
}
Reducer helper
The second part of redux-fetch-requests is a reducer helper which allows to get rid of plumber code for API requests processing in reducers.
The redux-fetch-request requestsReducer helper:
process defined <ACTION_TYPE> as well as correspond success, error and cancel actions
- initialize default state with object like {data,erro,pending}
export default const reducer = requestsReducer({
actionType: GET_BOOK
});
initial state:
{
data: null,
error: null,
pending: 0,
}
keeps the pending field in accordance with request/(success|error|cancel) counter
// state after GET_BOOK
{
data: null,
error: null,
pending: 1,
}
// state after GET_BOOK_SUCCESS
{
data: {...},
error: null,
pending: 0,
}
// state after GET_BOOK_ERROR/GET_BOOK_CANCEL
{
data: null,
error: {},
pending: 0,
}
allows to work with arrays
export default const reducer = requestsReducer({
actionType: GET_BOOKS
multiple: true,
});
initial state:
{
data: [],
error: null,
pending: 0,
}
allows to define default data object via getDefaultData option
export default const reducer = requestsReducer({
actionType: GET_BOOK,
getDefaultData: (options) => ({title:''})
});
// state after GET_BOOK_SUCCESS
{
data: {title: 'cool book'},
error: null,
pending: 0,
}
// initial state:
{
data: {title: ''},
error: null,
pending: 0,
}
allows to define result data processing via getData option
export default const reducer = requestsReducer({
actionType: GET_BOOK,
getData: (state, action, options) => ({
...action.data,
section: 'Fiction',
}),
});
// state after GET_BOOK_SUCCESS
{
data: { title: 'cool book', section: 'Fiction' },
error: null,
pending: 0,
}
allows to define error data processing via getError option
export default const reducer = requestsReducer({
actionType: GET_BOOK,
getError: (state, action, options) => new CustomError(action.error),
});
// state after GET_BOOK_ERROR
{
data: null,
error: CustomError,
pending: 0,
}
allows to define action(s) to reset store to initial state (this action can be the same as Request)
export default const reducer = requestsReducer({
actionType: GET_BOOK,
getDefaultData: (options) => ({title:''}),
resetOn: [GET_BOOK],
});
// state before GET_BOOK
{
data: {title: 'cool book},
error: null,
pending: 0,
}
// state after GET_BOOK
{
data: {title: ''},
error: null,
pending: 1,
}
License
MIT