Handling API calls in Redux with Redux API middleware


The context

It is very likely that, at a given point during the development of a client-side web app, it will be necessary to communicate with the server side using a RESTful API. If your stack is based on React/Redux, you may have found yourself wondering about where the ideal spot is to call your API and how to handle your application state based on the outcome of the interaction.

The problem

Having decided that we’re using Redux, calling the API from a container component (in the componentWillMount method for example) is not really an option because we want our Redux store to be the only place where the application state is held. Also, this would make our component harder to test, because we would be creating a dependency on the API that would then need to be mocked.

We also know that a reducer isn’t a good place, because it has to be a pure function with the sole responsibility of transforming the current state into the next state according to a given action. Hence side effects, like API calls, are not allowed.

A solution: using thunks

Apparently, by a process of elimination, a good spot to make our API calls is inside the action creators. Action creators are meant, by default, to synchronously return actions as plain objects. Although it is possible to implement an async flow with plain action creators, it is much more advisable to use a middleware like Redux Thunk or Redux Promise. This way, our components “aren’t aware of how action creators are implemented, and whether they care about Redux state, whether they are synchronous or asynchronous, and whether or not they call other action creators”.

An example using Redux Thunk middleware

A quite common way to make API calls using Redux Thunk is the following:

In this example the last action creator returns a function (thunk) that receives the dispatch method as a parameter and is therefore able to dispatch actions asynchronously. What we usually want when we call an API endpoint is to dispatch an action immediately to notify our app that a request is pending (and show a loader for example). Once the request is handled by the server, we want to dispatch either a success action (along with the returned data) or an error action (in order to notify the user that something went wrong).

There’s nothing wrong with this approach, but as our application grows we may want to try a different solution that will save us from unnecessary boilerplating and can give us some nice extra features to control the way we interact with our APIs.

A different approach: using Redux API middleware

A bit of history

Even if the examples provided in the Redux documentation about async actions make use of Redux Thunk, in the “real world” examples of the same repository, API calls are implemented using a custom API middleware. The concepts behind this example eventually evolved into the Redux API middleware.

This middleware relies on the core concept of RSAA. Let’s have a quick look at it.

RSAAs

RSAA stands for Redux Standard API-calling Action and it’s a specification for structuring actions that lives alongside the more familiar Flux Standard Action (FSA).

An RSAA is identified by the presence of a [CALL_API] property. This property is a Symbol defined in the middleware itself.

In order to have a valid RSAA the [CALL_API] property must be a plain object and contain the following properties:

  • endpoint (a string or a function returning a string),
  • method (a string amongst: GET, HEAD, POST, PUT, PATCH, DELETE or OPTIONS),
  • types (an array of length 3 containing strings or Symbols representing FSAs).

Let’s see a minimal example from the docs to clarify that:

Lifecycle

Every action dispatched within the application will be passed through every registered middleware. To use it in conjunction with other middlewares, like the Redux Thunk for example, we just have to specify the Redux API Middleware when we configure the store:

If the action is recognised as an RSAA (due to the presence of the [CALL_API] property), it will be processed by Redux API middleware, otherwise it will be transparently passed to the next middleware.

If the action is recognised as an RSAA by the middleware, the first FSA (in the example ‘REQUEST’) will be dispatched before making the ‘GET’ to the specified endpoint.

If the request is successful, the second FSA (‘SUCCESS’) is dispatched together with the data sent from the server in the payload. Otherwise, the third FSA is dispatched with an object describing the error as its payload.

This means we can now rewrite the Redux Thunk example from above like this:

As you can see, a lot of the boilerplating has gone. We can, of course, import our actions from other modules and have, for example, a generic handler for request failures, if we want. Also, notice that we removed the isomorphic-fetch import, as it is used internally by the middleware.

Other goodies

Error handling

Besides the error handling for a response with a status code outside of the 200 range, the middleware will also handle errors for non-valid RSAAs and for other scenarios, such as a network failure. In every case, the dispatched FSA will contain an error in the payload.

This error will be an object of a class that extends the native Error class and it will be amongst these: InvalidRSAA, InternalError, RequestError, ApiError.

FSA customisation

Another useful feature of the library is the ability to deeply customise the dispatched FSAs. The specified FSA can, in fact, be an object that can contain, besides the type, a payload and/or a meta property.
If the payload is specified as a function, this function will be able to access the action itself and the application state. This allows us to pass all the information we want inside our action.

The meta property is useful to pass through other info, when there’s no need to access the action or the state. In the above example, it’s used to track where the action has been called. Both the payload and the meta properties accept Promises and the middleware will automatically wait for them to be resolved before dispatching the action.

The FSA type descriptors are highly customisable and if you’re curious about this feature check out the extensive documentation.

Bailout

Besides the mandatory property mentioned before, an RSAA may also have the following properties:

  • body
  • headers
  • credentials
  • bailout

The first two are quite self explanatory and the third one is used to specify whether or not to send the cookies.

The bailout property, on the other hand, offers us the possibility to skip the dispatching of the action. We can do that by assigning a function to this property that receives the application state and returns a boolean. This can be useful, for example, to implement an offline mode or a role-based access control.

Conclusions

The example we saw should be enough to understand how this middleware can help us avoiding boilerplating when dealing with remote API communication. I also find the extra features offered by the middleware extremely useful, such as the advanced error handling and the ability to bailout when necessary.

A fundamental selling point, for me, was the fact that non RSAA actions are passed unaltered to the next middleware. This means that the Redux API middleware can be added to an existing code base without the need for any refactoring and it will not affect the way you decided to write your non-API-related portions of the application.

That’s all folks!

About the author

Francesco is a developer working with Trainline. He likes to work with web browsers. Nowadays, he has fun working with React, React Native and Redux. He also tries hard to keep a non-over-opinionated/dogmatic/religious approach to all things development related!

 

2 thoughts on “Handling API calls in Redux with Redux API middleware

  1. I don’t see how you add the data to the store.
    There are no reducers here that I can see, just actions.
    I’m trying to figure out how the library works for use in a project I’m working on.

    • The reducer is very straight forward. In this case, for example:
      switch(action.type) {
      case ‘REQUEST’:
      return {
      …state,
      loading: true,
      }
      case ‘SUCCESS’:
      return {
      …state,
      data: action.payload.data,
      loading: false,
      }
      ….
      default:
      return state;
      }

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s