In order to make the API calls to the back end, we’ve been using the redux-api-middleware library which works quite well for us. We’ve added a bit of infrastructure that makes communicating with a Rails server a bit easier.
As with some of my earlier articles, this code is part of Zeal’s react-boilerplate.
redux-api-middleware is a Redux middleware that handles specially-formatted Redux actions. It exposes a special property name (
CALL_API) and any action that contains that property is handled by the middleware. The value of
CALL_API is a descriptor that specifies how an API call should be made and what Redux actions to dispatch at various stages of the process. The possible actions are a
REQUEST action that is dispatched just before making the API call, a
SUCCESS action that is dispatched when the call succeeds, and a
FAILURE action that is dispatched when the call fails.
Here’s a minimal example of a redux-api-middleware action, taken from the README:
Problems to Solve
As we began working with Redux and Rails, we started running into a fair bit of duplicated code and wanted a way to extract it into one place. Duplicated pieces included:
- Converting payloads to “stringified” JSON.
- Adding common request headers
The version of this code in the react-boilerplate repository is somewhat more involved than this, as it solves a couple of other problems and also injects some dependencies to make testing easier. I’m leaving those out to make the code easier to understand.
To eliminate duplication, we introduced a utility function,
callApi, that we use when making calls to our Rails application’s API:
callApi takes a redux-api-middleware call descriptor, transforms it, and returns a Redux action that can be dispatched through the middleware. I’ll talk about
transformCallDescriptor in a moment.
We can already see that none of the callers of
callApi need to worry about the
CALL_API property name; that’s now encapsulated in this function. The callers still need to know the format of the call descriptor, but they are now slightly less coupled to redux-api-middleware.
A simple use of
callApi in a Redux action creator might look like this:
transformCallDescriptor looks like this:
As I write this, I see that I could extract some named helper functions that would make this a bit clearer. What is this doing?
First, we use some Ramda functional magic to define a local
transform function and then we apply that to the call descriptor.
composeto build up a function that applies multiple transformations. Saying
compose(f, g, h)(argument)is the same as saying
f(g(h(argument))). We call
hwith the argument, pass the result of that to
g, the result of that to
f, and return the result of that as the final result.
Reading from the bottom up, we supply a default header that sets the
application/json. We could just use
mergehere, but then any headers provided by the caller would overwrite our header. Instead, we want to merge caller-supplied headers with our header, so we use
merge. The caller can provide its own
Content-Typeheader and that will be used, but if it doesn’t, we’ll use our default value.
Next, we default the HTTP method to
GET. Again, the caller can provide a different method, but if none is given, we default to
Finally, we use Ramda’s
evolvefunction to modify some of the properties passed in by the caller.
evolvetakes an object that specifies a transformation function for each key. Here we’re saying we want to transform the
We transform the
bodyby converting all of the keys from camelCase to snake_case using the humps library’s
decamelizeKeysfunction. We then take the result and call
JSON.stringifyto convert the body to a JSON string.
We then transform several of the elements of the
typesarray using Ramda’s
typesis the array of three action type descriptors that I mentioned above. The first specifies the
REQUESTaction that will be dispatched by redux-api-middleware; the second specifies the
SUCCESSaction; and the third the
FAILUREaction. See the Lifecycle documentation of redux-api-middleware for more details.
Let’s look at how we transform the
SUCCESS action first:
As described in Customizing the dispatched FSAs documentation, redux-api-middleware allows the
SUCCESS type descriptor to be a string or symbol OR an object that serves as a blueprint for creating the Redux action.
We’re not sure which version our callers will supply, so we have a little utility function called
objectize that ensures we have an object.
contains is another Ramda function.
We then merge in a new
payload property using ES7 object-spread notation. We could have used Ramda’s
merge here as well but for simple things like this, I find object-spread more readable.
payload property is a function that does exactly what redux-api-middleware would do (using its
getJSON utility function), but then uses humps’
camelizeKeys to transform the keys in the response from snake_case to camelCase. This is the reverse transformation of the one we applied to the
transformFailurePayload is almost identical, but we need to wrap the transformed response in a redux-api-middleware
ApiError to match the default behavior:
callApi, we can now simplify the code we use to make API calls and remove a bunch of copy-pasted logic.
We’ve added other transformations on some of our client projects. For example, in one case we also transform the
endpoint property to prepend a hostname and common URL prefix. In another case, we look up the API authorization token and merge in an
Authorization header along with the
Content-Type header described above.
This little set of utility functions allows for a lot of common boilerplate to be extracted to one place instead of being scattered all over the code.