react-redux-async-action

Helper methods for dealing with boilerplate of working with react-redux actions that have an eventual outcome

Usage no npm install needed!

<script type="module">
  import reactReduxAsyncAction from 'https://cdn.skypack.dev/react-redux-async-action';
</script>

README

react-redux-async-action

Build Status Coverage Status npm NPM

A set of helper functions to reduce the boilerplate of working with redux actions that trigger an async call and end up dispatching a sucess or failure of said action upon completion

Installation:

npm install --save react-redux-async-action
yarn add react-redux-async-action

Motivation

When using redux I often run into this scenario, say we want to fetch some data so we need the action to trigger the call from a component:

export const FETCH_DATA = 'FETCH_DATA';

export const fetchData = (id)=> ({ type: FETCH_DATA, id });

But we also need the sucess and failure variants to dispatch when the call completes:

export const FETCH_DATA_SUCCESS = 'FETCH_DATA_SUCCESS';
export const FETCH_DATA_FAILURE = 'FETCH_DATA_FAILURE';

export const fetchDataSuccess = (data)=> ({ type: FETCH_DATA_SUCCESS, data });
export const fetchDataFailure = (error)=> ({ type: FETCH_DATA_FAILURE, error });

So if we were using thunks, we would need something like this:

async function getDataFromRestApi(id) {
  ...
  return [];
}

export const fetchDataThunk = (id) => (dispatch) => {
  dispatch(fetchData(id));
  getDataFromRestApi(id)
    .then(result=> {
      dispatch(fetchDataSuccess(result));
    })
    .catch(error=> {
      dispatch(fetchDataFailure(error));
    });
};

and your reducer would look something like this:

import { FETCH_DATA, FETCH_DATA_SUCCESS, FETCH_DATA_FAILURE } from './actionTypes';

switch(action.type) {
//...  
  case FETCH_DATA:
    return {...state, fetchingData: true};
  case FETCH_DATA_SUCCESS:
    return {...state, fetchingData: false, data: action.data};
  case FETCH_DATA_FAILURE:
    return {...state, fetchingData: false, fetchError: action.error.message};
//...  
}

This becomes quite repetive when you have different async actions on your application and this aims to alliviate that.

Usage

An important convention

The shape of all your actions should be the same:

{ type, payload, error }
  • type which is the string constant that identifies the action
  • payload can be anything, an object, a string, etc
  • error is optional and will be provided in case its a _FAILURE action

Now you only really need to declare the action:

import { success, failure, asThunk } from 'react-redux-async-action';

export const FETCH_DATA = 'FETCH_DATA';
const fetchData = (id)=> ({ type: FETCH_DATA, payload: id });

and your thunk

export const fetchDataThunk = asThunk(fetchData, (payload)=> getDataFromRestApi(payload.id));

Your reducer would be similar as before, but you can use the sucess and failure helpers on the action type which looks a little bit more semantic.

import { FETCH_DATA } from './actionTypes';
import { success, failure } from 'react-redux-async-action';

switch(action.type) {
//...  
  case FETCH_DATA:
    return {...state, fetchingData: true};
  case success(FETCH_DATA):
    return {...state, fetchingData: false, data: action.data};
  case failure(FETCH_DATA):
    return {...state, fetchingData: false, fetchError: action.error.message};
//...  
}

This convention allows you to handle all failure actions on one place if you want to:

  if(action.type.endsWith('_FAILURE'))
    return {...state, errors: [...state.errors, action.error]};

Available helpers in the package

success

Suffixes the _SUCCESS string to the action's type constant if it's a string or action's type property if it's an action object

function success(action)
parameter type description
action string
object
The action constant string e.g. 'FETCH_DATA'
The action object e.g. { type: 'FETCH_DATA' payload: { id: 42 }}

failure

Suffixes the _FAILURE string to the action's type constant if it's a string or action's type property if it's an action object.
It also adds an error object to the action representing the error that caused the failure

function failure(action, error)
parameter type description
action string
object
The action constant string e.g. 'FETCH_DATA'
The action object e.g. { type: 'FETCH_DATA' payload: { id: 42 }}
error Error The exception that caused the failure

asThunk

Creates a thunk that calls an async function and dispatches success or failure actions based on the outcome.
It can be customized to do additional logic on either case.

function asThunk(actionCreator, asyncCall, afterSuccess, afterFailure)
parameter type description
actionCreator function(payload) The action creator function to create a thunk for
asyncCall function(payload) The async function to execute when the action is dispatched
its return value will be provided to the _SUCCESS action as its payload
afterSuccess function(action, result, dispatch) (optional) The function to execute after the async call succeeds and _SUCCESS action is dispatched
action: the originally dispatched action
result: the return value of the asyncCall function
dispatch: the dispatch function in case you want to dispatch additional actions
afterFailure function(action, error, dispatch) (optional) The function to execute after the async call fails and _FAILURE action is dispatched
action: the originally dispatched action
error: the error caught from the asyncCall invocation
dispatch: the dispatch function in case you want to dispatch additional actions