Redux - Calling web service asynchronously

The store in Redux should be synchronous and should not have any side effects.
How can you then make service calls or do anything asynchronously?

That is the question I was asking myself when I started using Redux with react-native. I needed to make calls to Google web services but in a pattern that conforms with flux/Redux way of doing things.

In Redux any asynchronous task should be part of the action itself and when it completes, the action to store should be dispatched, that is the pattern used for asynchronous tasks.

When you make a service call you also want to know whether it is in progress, has completed or if there was any error so that you can make the necessary UI changes. You want to store that information hence you will have a reducer created to maintain the state of service calls or use it to update your current state.

Redux is a very minimal library but it provides the functionality to extend it using middleware. There is a whole bunch of middleware created for redux.
Redux thunk is a middleware which is used to delay the dispatch of the action. We will use that to dispatch action before and after our service call.

Let's imagine we want to call the Marvel API to get list of comics and we have this helper function already created MarvelAPI.get_comics() which returns a promise to the API call.

Also suppose we have the following actions for different state of calling the service

  1. GET_COMICS_PENDING when the request is sent.
  2. GET_COMICS_FULFILLED when the response was successfully received.
  3. GET_COMICS_REJECTED when there was an error.

Following is how we would call the API in our action.

export function get_comics() {
  const action_type = "GET_COMICS";
  return dispatch => {
    // Dispatch the action for telling our reducer 
    // that the API call is in progress
    dispatch({type: `${action_type}_PENDING`});
    MarvelAPI.get_comics()
      .then(resp => {
        // Dispatch the success action with the payload
        dispatch({
          type: `${action_type}_FULFILLED`,
          payload: resp.data.results
        });
      })
      .catch(err => {
        // Dispatch the error action with error information
        dispatch({
          type: `${action_type}_REJECTED`,
          error: err
        });
      });
  };
}

Here when we call the get_comics action we first dispatch the GET_COMICS_PENDING action, then we call the Marvel API and as its continuation we dispatch the action GET_COMICS_FULFILLED with the payload in case of success otherwise we dispatch the GET_COMICS_REJECTED with the error.
Following is how our reducer will handle all the actions

const initial_state = {
  comics: [],
  inProgress: false,
  error: null
};
const action_type = "GET_COMICS";

export function comics_reducer(state = initial_state, action) {
  switch(action.type) {
    case `${action_type}_PENDING`:
      return { 
        ...state, 
        inProgress: true,
        error: false
      };
    case `${action_type}_FULFILLED`:
      return {
        ...state,
        comics: action.payload,
        inProgress: false
      };
    case `${action_type}_REJECTED`:
      return {
        ...state,
        inProgress: false,
        error: action.error
      };
  }
}

The reducer is pretty simple, we add the comics information in case it's successful otherwise in case of failure the error information is added.

This does the job but let's suppose you have multiple such API calls then writing all that boilerplate would be tedious. That is where we will use another middleware that does the job for us.

The middleware we will use is redux-promise-middleware. It uses Flux Standard Action which is a standard way to create actions. This enables different tools to rely on the action schema and build better experience for the user. To create these actions we will use redux-actions. You can always create it by hand as well but that library just makes it easier.

import { createAction } from 'redux-actions';

export const get_comics = createAction("GET_COMICS", () => {
  return {
    promise: MarvelAPI.get_comics()
  };
});

This is all you have to do for creating the action and our existing reducer would work. The redux-promise-middleware expects an action that looks like following

{
  type: // The type
  payload: {
    promise: // A promise, could be a service call or some async storage call
  }
}

It will then resolve that promise and also dispatch the appropriate actions i.e. GET_COMICS_PENDING, GET_COMICS_FULFILLED and GET_COMICS_REJECTED that we need to handle in our reducer.

redux-promise-middleware will suffix the action type GET_COMICS with _PENDING, _FULFILLED and _REJECTED depending on what the promise returns.

This will make depending on asynchronous tasks a lot easier. I still have to figure out how to tie up multiple service calls into one reducer.