How to setup Redux for a REST api

A guide on how to setup Redux in a generic way to reduce boilerplate and facilitate up to 90% of your API calls

Onoufrios Malikkides
HackerNoon.com
Published in
9 min readMar 31, 2019

--

Motivation

I gave a talk at the React London Meetup on state management with Redux and I wanted to write an article explaining in more depth the architecture of the solution I proposed.

You can also find a Github repo with a full Redux setup, ready to be used in a real world React application at the end of this article, as well as a video of my talk.

The problem

Redux is an amazing library. It’s very simple and powerful at the same time. It is also very flexible. This is great because it gives us freedom to set it up the way we want and shape it to serve the needs of our application. But this flexibility also makes it difficult to integrate it for a large application because there are so many ways we can do this.

The general consensus on how to setup Redux to do an api call is to:

  • Declare constants (REQUEST_ACTION, SUCCESS_ACTION, FAIL_ACTION)
  • Create an action creator which will be responsible to dispatch the request action
  • Use an API service to call endpoint (Using a third party library like redux-thunk, saga, observables to enable that)
  • Receive a payload. If successful, dispatch SUCCESS_ACTION, if not then dispatch FAIL_ACTION
  • Handle payload in reducer
  • Connect redux to react to the component (using mapStateToProps and mapDispatchToProps)

And this has to be repeated for every single API call. This quickly results in a lot of repeated boilerplate and unmaintainable code.

Solution

Let’s see how we can solve the problem described above by following an example to read a single user.

Action Creators

Now the first thing we would typically do if we wanted to read a user would be declare some constants as below:

The first improvement we are going to do is to completely get rid of these! We don’t really need to declare them as constants. We can easily compute them dynamically and with a few unit tests make sure that everything works as expected.

The next thing we would typically do would be to define an action creator like so:

This action creator uses redux-thunk to first send a request action, then does the API call and fires a success or fail action depending on the response. But there are two problems with this code:

  1. This action creator is very specific to the user entity. The functionality for reading entities will be mostly the same so defining an action creator for just the user only is not very reusable. If we wanted to read a group entity for example the code would be almost identical.
  2. We are mixing concerns by firing both the request action but also doing the api call. The action creator should just be responsible for sending an action.

Let’s see how we can fix these problems. The action creator was re-written in a way that its sole responsibility is to fire a REQUEST action. At the same time it is now able to be used for any entity in the system. This is how it looks like:

As you can see by the name of this action creator, it is generic (as opposed to readUser in the previous section). It can be used by any entity in the system. This action creator takes two arguments: The entityName (this could be user , group , post e.t.c) and the ID of the entity we want to read. This action creator is firing an action with the following fields:

  1. The type, which will be REQUEST_READ_${entityName} (We are making entityName uppercase just for uniformity)
  2. The urlParams, which will hold the parameters that we will use to compute the API endpoint we will call.
  3. The meta data, which will be used by our reducers and middlewares.

So what we’ve achieved here is to create an action creator that works with any entity in the system, the type is computed dynamically so we don’t need constants any more and also it’s only purpose is to return an action.

API Middleware

Next we want to create an API middleware where we will put all the logic for sending the API call and then dispatching a success or fail action depending on the response.

And this is what it will look like:

Here we are listening on any action that its type starts with REQUEST_READ (e.g. REQUEST_READ_USER ), then we are calling the API endpoint and depending on the response firing a SUCCESS or FAIL action.

The two thing to note here are:

  1. The urlParams which were defined in the request action in the previous section are used to compute the api endpoint.
  2. The action type is again computed dynamically.

So, this API middleware works for any entity in the system, eliminates the action type constants and the use of any external libraries to enable us to do our API calls.

Normalization Middleware

Next we should normalize the response we get from the API. Before we see how we can do this let’s see what normalization is:

Many applications deal with data that is nested or relational in nature. An example is shown below:

Payload with nested data (before normalization)

This is a post entity, which has an author and some comments (which themselves have an author).

With normalization we can return the nested entities with their IDs, gathered in dictionaries. Below you can see the normalized version of the post .

Normalized Payload

All nested data now refer to an entity through the id key.

There is a major benefit to normalize data in that updating becomes very easy. If for example we change the name of the user with id 1 from ‘Jeff’ to ‘Peter’ then with the first approach we need to update it in 2 places. But with the normalized data we only need to update it in one place. You can see how the first approach can become unmaintainable in a large application. In the unnormalized version we could end up with a situation of having to update the state in 20 different places!

The middleware to normalize the payload will look like this:

Here we are listening on any action that its type starts with SUCCES_READ (e.g. SUCCES_READ_USER ) and then normalizing the payload data.

Note: For all of our normalization needs, we will use the normalizr library

Reducers - Store structure

This is how we are going to structure our store:

All the data coming from our api will live under the entities key. Under there we will keep a key for every entity in the system (e.g. user, post e.t.c).

Under each entity key we will have:

  1. A byId key where we will keep the payload coming back from our API (in a normalized structure).
  2. A readIds key where we will keep the status of the api calls.

To achieve this we will need a reducer creator. A reducer creator is a function that takes an argument and returns reducers as shown below:

This getReducers reducer creator takes the entityName argument and returns two reducers (using the combineReducer function of redux library).

Important Note:

We will have to call this reducer creator for every entity in the system. So every entity will have its own byId and readIds reducer. This is important to understand as we explain how the reducers work. Every redux action goes through all the reducers. What that means is that in every byId and every readIds reducer we would have to only consider the action types or action payload that is relevant to the entity that this reducer corresponds to. It’s also a good idea to see the Github repo (at the end of this article) in order to fully understand how these reducers work.

byId Reducer

The byId reducer will listen on any action type that starts with SUCCESS (e.g. SUCCESS_READ_USER ) and if in our payload there is data of this entity type, it will be merged in the state. If for example our payload looks like the data in the Normalized Payload above and this is the byId reducer of the comment entity the comment part of the payload will be merged into the state.

readIds Reducer

Here we are listening to any action that it’s type starts with READ and that is the relevant readIds reducer. If for example the action is REQUEST_READ_USER and this is the readIds reducer of the user entity we will go ahead and update the state.

Connect Higher Order Component

The last thing I want to do is to reduce the boilerplate when I’m connecting my redux code with my react code. Typically in every React component we wanted to connect with redux we would have to use the connect library of react-redux and then define a mapStateToProps and a mapDispatchToProps function to achieve this. In order to achieve reusability I want to move this code in a higher order component (HOC) that will look something like this:

This HOC received two arguments. The entityName and an id . It then passes to our children 3 arguments:

  1. A read function which calls the readEntity action creator we saw in the previous sections.
  2. The entity which is the data selected from the relevant byId reducer.
  3. The status which is the status of the api call selected from the relevant readIds reducer.

And this HOC can be used like this:

In line 22 we are implementing the HOC seen above. We pass the two required arguments. The entityName , which in this case it will be user as we want to read a user. And the id which in this case is 1 as we want to read the user with id 1.

The HOC gives us the three arguments (read, status, entity). Then in our User component we can call read when the component mounts. We can also use the status to see if the api call has finished. If it has then we show the user name otherwise we can show a Loading.. text.

Here we can see how much we have reduced the boilerplate in our react components. The only thing we need to connect react with redux and do a read api call is to use this HOC.

Recap

  1. We got rid of the action type constants
  2. We defined a generic readEntity action creator that can be used by any entity in the system and its sole responsibility is to fire the request action.
  3. We defined an api middleware to call our endpoint and fire success or fail action depending on the response.
  4. We defined a normalize middleware responsible to normalize our payload.
  5. We defined a getReducers reducer creator that return two sub-reducers byId and readIds
  6. We moved the logic to connect our react components with redux in a Higher Order Component.

Achieve 90% coverage (or more)

It’s important to notice that all the code above is very generic. That means that we can read any entity in the system without writing any additional code. But to achieve 90% or more coverage of our api call needs we have to handle all the following cases:

  1. Create
  2. Read
  3. Update
  4. Delete
  5. Attach or detach an entity from another in a many to many relationship.

And we have to allow for single or multiple entities (e.g. we should be able to read a single user or multiple users, or delete one post or multiple posts at once).

And actually in order to achieve this there is not a lot of additional code needed.

You can find the entire codebase in the Github repo below.

Github Repo

This repo serves as a guide on how to setup Redux in a generic way. It’s an MIT licensed repo so feel free to use it as is, fork it or use it as an inspiration.

Video of React London meetup talk

--

--