Skip to content

Reaverart/redux-callapi-middleware

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

39 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

redux-callapi-middleware

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.

Contents

  1. Example
  2. Usage and Docs
  3. Installation
  4. Create middleware
  5. Action creator
  6. Lifecycle
  7. Dispatched FSAs
  8. FAQ
  9. Acknowledgements
  10. License

Example

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.

Single typed action

// 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
}

Different action type

// 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
}

Usage and Docs

Installation

  1. Install redux-callapi-middleware through npm:
$ npm install redux-callapi-middleware --save
  1. 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)
);

Create middleware

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

Action creator should return an object with [CALL_API] property with batch, endpoint, options and types fields. See example.

[CALL_API].batch

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 },
],

[CALL_API].queue

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.

[CALL_API].endpoint

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',

[CALL_API].options

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'},

[CALL_API].types

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'
]

[CALL_API].type

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
}

How it works

  1. Checks if action has [CALL_API]. If no it stops and dispatches action to next middleware.
  2. Builds request endpoint and options. There might be error handling in future.
  3. Dispatches to next middleware request FSA from first item of [CALL_API].types.
  4. Performs API call by request params.
  5. Checks response status with checkStatus function (see create middleware). If succeed it will try to parse response with parseResponse 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.

Dispatched FSAs

The [CALL_API].types array can hold 4 kind of actions types:

  • strings - will be converted to FSA object.
  • symbols - same as strings.
  • object - it should be valid FSA object.
      {
        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 FSA object.
      (apiAction, state, payload) => ({
        type: 'SUCCESS',
        payload
      })

Request FSA

Not receives payload as FSA property or function argument from middleware. (There is no payload at this moment)

Success FSA

Receives response as payload, it will be converted to json or text by middleware.

Failure FSA

Receives error as payload, response attached to error.response property. FSA also have error flag set to true.

FAQ

  1. 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'
        }
      }
    })
)
  1. 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'
    }
  }
}
  1. 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'
    }
  }
}
  1. Need a promise as output action?

Not supported, but might work with redux-promise.

  1. Difference with redux-api-middleware?

  2. It dispatches errors only with error type

  3. It not dispatches "programmatic" errors, like errors on endpoint generation.

  4. It gives more control with functions as actions types

  5. Not supports promises, but take look to redux-promise.

  6. Allows to batch API calls

  7. Want to have base URL?

Write a wrapper around your callApi action creator.

  1. Want to check custom headers or have custom parse response?

See create middleware

  1. Wish to have custom error handling?

See create middleware

Acknowledgements

Originally inspired and extracted from Dan Abramov the real-world sample in the redux repository. Thanks to all developers and contributors.

License

The MIT License (MIT) Copyright (c) 2017 Artur Charaev