npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

redux-fetch-requests

v1.0.0

Published

Redux middleware to simplify handling of AJAX requests

Downloads

4

Readme

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