How to Setup Redux - Minimize Boilerplate, Speed Up Development

How to Setup Redux - Minimize Boilerplate, Speed Up Development

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

Motivation

I recently 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, REQUEST_ACTION, REQUEST_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 REQUEST_ACTION, if not then dispatch REQUEST_ACTION
  • Handle payload in reducer
  • Connect redux to react to the component (using REQUEST_ACTION and REQUEST_ACTION)

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:

export const REQUEST_READ_USER = 'REQUEST_READ_USER';
export const SUCCESS_READ_USER = 'SUCCESS_READ_USER';
export const FAIL_READ_USER = 'FAIL_READ_USER';

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 action, then does the API call and fires a REQUEST_ACTION or REQUEST_ACTION action depending on the response. But there are two problems with this code:

  1. This action creator is very specific to the REQUEST_ACTION 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 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 REQUEST_ACTION in the previous section). It can be used by any entity in the system. This action creator takes two arguments: The REQUEST_ACTION (this could be REQUEST_ACTION , REQUEST_ACTION , REQUEST_ACTION 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_ACTION (We are making REQUEST_ACTION uppercase just for uniformity)
  2. The REQUEST_ACTION, 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_ACTION (e.g. REQUEST_ACTION ), then we are calling the API endpoint and depending on the response firing a REQUEST_ACTION or REQUEST_ACTION action.

The two thing to note here are:

  1. The REQUEST_ACTION 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 REQUEST_ACTION 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 REQUEST_ACTION .

Normalized Payload

All nested data now refer to an entity through the REQUEST_ACTION 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 REQUEST_ACTION 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 REQUEST_ACTION (e.g. REQUEST_ACTION ) 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 REQUEST_ACTION key. Under there we will keep a key for every entity in the system (e.g. REQUEST_ACTION, REQUEST_ACTION e.t.c).

Under each entity key we will have:

  1. A REQUEST_ACTION key where we will keep the payload coming back from our API (in a normalized structure).
  2. A REQUEST_ACTION 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 REQUEST_ACTION reducer creator takes the REQUEST_ACTION argument and returns two reducers (using the REQUEST_ACTION function of REQUEST_ACTION library).

Important Note:

We will have to call this reducer creator for every entity in the system. So every entity will have its own REQUEST_ACTION and REQUEST_ACTION 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 REQUEST_ACTION and every REQUEST_ACTION 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 REQUEST_ACTION reducer will listen on any action type that starts with REQUEST_ACTION (e.g. REQUEST_ACTION ) 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 REQUEST_ACTION reducer of the REQUEST_ACTION 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 REQUEST_ACTION and that is the relevant REQUEST_ACTION reducer. If for example the action is REQUEST_ACTION and this is the REQUEST_ACTION reducer of the REQUEST_ACTION 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 REQUEST_ACTION and then define a REQUEST_ACTION and a REQUEST_ACTION 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:

export const REQUEST_READ_USER = 'REQUEST_READ_USER';
export const SUCCESS_READ_USER = 'SUCCESS_READ_USER';
export const FAIL_READ_USER = 'FAIL_READ_USER';

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

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

And this HOC can be used like this:

export const REQUEST_READ_USER = 'REQUEST_READ_USER';
export const SUCCESS_READ_USER = 'SUCCESS_READ_USER';
export const FAIL_READ_USER = 'FAIL_READ_USER';

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

The HOC gives us the three arguments (read, status, entity). Then in our REQUEST_ACTION component we can call REQUEST_ACTION 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 REQUEST_ACTION 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.

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.

redux reactjs javascript

Bootstrap 5 Complete Course with Examples

Bootstrap 5 Tutorial - Bootstrap 5 Crash Course for Beginners

Nest.JS Tutorial for Beginners

Hello Vue 3: A First Look at Vue 3 and the Composition API

Building a simple Applications with Vue 3

Deno Crash Course: Explore Deno and Create a full REST API with Deno

How to Build a Real-time Chat App with Deno and WebSockets

Convert HTML to Markdown Online

HTML entity encoder decoder Online

How to implement Redux Saga with ReactJS and Redux?

In this article, I’d like to tell you more about the Redux library and create a simple ReactJS project, where I’ll set up a Redux step by step.

Should I Use Redux?

Starting a new application means drawing out a blueprint of what you want it to look like, and thinking about how you are going to implement whatever you are going to do before you even write any code. As a JavaScript developer, I often find myself questioning whether or not I’ll need to use Redux.

How to implement redux-saga with ReactJS and Redux [TUTORIAL]

In this article, we will go throug theory about state, state management tools like Redux, middleware, Redux Thunk and Redux Saga.

Redux Tutorial - Learn Redux from Scratch

Redux Tutorial - Learn Redux from Scratch - You'll learn: What is Redux? What is Functional Programming? Higher-order Functions; Functional Composition; Redux Architecture... Redux - A Predictable State Container for JS Apps. Redux is an open-source JavaScript library for managing application state. It is most commonly used with libraries such as React or Angular for building user interfaces. Similar to Facebook's Flux architecture, it was created by Dan Abramov and Andrew Clark.

Top Startups and Enterprises Using ReactJS

React is a framework released by Facebook for creating Single Page Applications (SPA). What is a Single Page Application? Most web applications are traditionally server-side applications. The server holds the business logic, stores data, and renders the website to the client. When a client clicks on a link, it sends a request to the server, and the server will handle this request and send back a response with HTML code which the browser will render and be viewed by the user.