Photo by Roksolana Zasiadko on Unsplash

TL:DR

See the following gist example if you are in a hurry. But if you have the time, read on to understand my motivation for this approach.

Background

I have been working with React and Redux for a few years and one of the challenging aspects is on how to layout a redux application. There are many conventions and “best practices” which include;

My personal favourite is the ducks approach, where the constants, action creators, reducers, middleware, utilities etc. are in one file or directory. This layout has quite a lot of advantages i.e. it reduces the number of files I have to navigate through to make a change, it’s quite modular and thus easier to reason about (for me anyway).

However, despite its benefits, I am finding that this approach can be frustrating when dealing with REST APIs.

The Problem: REST APIs

The REST architectural style is quite powerful and is one of the more dominant ways that a client application communicates with the server. Despite the fact there are more modern alternatives such as RPC or GraphQL, REST still captures a significant app share of the client to server communication, and it’s what I always use in my apps.

I won’t go into too much depth about the REST specification as there are quite a few of articles out there concerning it e.g. https://restfulapi.net/, but to understand my frustration of using it with redux one needs to be familiar with the main HTTP methods used which are: HTTP GET, HTTP POST, HTTP PUT, HTTP DELETE, HTTP PATCH.

In addition, according to one of the 6 guiding constraints of REST one of the key constraints is that all REST APIs must have a uniform interface. This means if you are building an app with different resources/endpoints e.g. a user resource, a feed resource or a comment resource etc. according to the uniform interface guiding constraint, each resource must have the same implementation of the GET, POST, PUT, DELETE and/or PATCH methods such that the way you call the url is the same and the data return format is also the same.

Why is this problematic in redux?

Assuming that the Ducks pattern is been followed, an app with N number of API endpoints will in theory have at least N number of redux ducks files or folders accounting for the different API endpoints e.g.

redux\
--user.js (/api/v1/user)
--feed.js (/api/v1/feed)
--comment.js (/api/v1/comment)
--...etc.js (/api/v1/etc)

What happens if we open and look into any of the redux files that communicates with a REST based API, chances are we’ll find a similar structure to the following e.g. focusing only on the GET method as an example.

Now open another file, I am sure if you follow the REST architecture faithfully, you will find that all of them share a similar structure. As a result there is quite a lot of code duplication in the application. In fact whenever I look through my redux folder, there is at least 80 % duplication when communicating with an API due to the uniform interface key constraint.

Redux Class Based Ducks Pattern

Having faced the frustration of multiple files/directories per API endpoint, I have made an attempt towards a solution by turning to Object Oriented Programming. To this end, I have been experimenting with a Class based approach in managing my redux application.

redux/api.js

The first file we will look at is an arbitrarily named redux/api.js file. The following is an example of a class called ReduxApi that manages the data lifecycle both in the redux application as well as from the API.

The class encapsulates all the functions necessary to communicate with the server using a REST API, as well as update the redux state container, in that:

  • The redux application is contained within a JavaScript ES6 class.
  • The constructor only takes 2 arguments, the name of the app and an api endpoint, which are set as object properties.
  • Also in the constructor, the reducer, selectors and saga methods are bound to the current scope i.e. this. (I found not binding them leads to errors).
  • There is a reducer functions that calls other functions which I have called action handler functions e.g. getSuccessReducerHandler. These handler functions are pure functions, and the abstraction is necessary to allow for method overriding in case one wants to modify the data returned in one of the API endpoints.
  • I have also called the redux-saga all function as well as the necessary generator functions utilized by redux-saga to sync with data the server.
  • In addition, selectors have also been encapsulated which are designed to compute derived data. In this example I am using a makeSelectBase that takes a string and returns the data with that particular key in the redux store. The makeSelectBase calls the selectGlobal function which gets the objects redux state container based on the app name.

redux/index.js

This approach bundles everything we need to make an API call and then afterwards update the redux state store into one object. This means for a variable number of api endpoints I only need ONE base redux ducks class and can instantiate it with a unique name and an endpoint. The unique name is used by the internal redux state store and the endpoint is used to communicate with the server. This idea is shown in the following example.

In the index file:

  • The ReduxApi class is added as an import.
  • Instances of the user, feed and comment endpoints are created with unique names and endpoints. The name must be unique so that whenever new actions are triggered, the appropriate redux state key is updated, e.g. user, feed or comment.
  • Once this is done, we can access the reducer and saga methods from the created objects and export them in a similar way as suggested in the re-ducks directory approach.

The benefit of this is that, if a new REST API endpoint is created, all you need to do is create a new instance of the ReduxApi class and add it to the default export and the saga export.

module/container.js

Now that we have attached the required reducer and saga functionality to the state store, how does one use the above functionality. Assuming I am in a component that requires user data e.g. a Header component, it will be as follows:

Again, all I have to do is import the redux api class, instantiate it with the identical arguments that was added to the configure state (they have to be identical otherwise the wrong redux state container key will be updated). The relevant method can then be attached in either the dispatcher and/or the createStructuredSelector method.

Extending Functionality

Suppose for our feed app, we want to map the returned date and convert them to date objects e.g. using moment.js. Since we are dealing with classes, we can simply create a subclass by extending the base class as shown below.

class ExtendedReduxApi extends ReduxApi {
getSuccessReducerHandler(state, action) {
return state
.set('requesting', false)
.set('initialized', true)
.set('list', action.data.map(x => ({ ...x, moment(x.date)}));
}
}

In the above example, we are overriding the getSuccessReducerHandler which is a pure function (a sort of mini reducer) that takes the initial state and returns a new state with a specific modification.

Suppose in another endpoint there is a need to have a different way of handling requested data such as triggering a separate action e.g.

class ExtendedReduxApi extends ReduxApi {* get(action = {}) {
try {
const resp = yield Axios.get(action.url || this.URL);
yield put(this.getSuccess(resp.data, action.append));

// Triggering a new action
const feed = new ReduxAPI('classes', '/api/v1/feed/');
yield put(feed.getSuccess(resp.data.feed))
} catch (err) {
yield put(this.getError(err));
}
}
}

By overriding the get generator, we can trigger a dispatch for a separate getSuccess function by creating a new instance of our base ReduxApi class with the appropriate name and endpoint and we can dispatch the required action. Note, when creating a new instance, the ReduxApi class is available so not in danger of having circular dependencies.

Advantages

I have only tried this project on a small codebase on hobby projects and some of the advantages of this approach I found include:

  • Code duplication is avoided which keeps the file footprint quite low, especially on projects where speed to market is crucial.
  • I found that the codebase generalizes well i.e. a small codebase can cover quite a lot of cases.
  • A very real example I found is that if something fundamental changes in the API endpoint e.g. adding pagination, only one file needs to be changed and tested. This is in contrast with a file or directory based approach where one would have to go through all the files and updating the codebase in all the files individually.

Disadvantages

I am not naive enough to think there are no disadvantages with this approach, they might be several, and most of them will involve the inherent flaws of Object Oriented Programming particularly applied to JavaScript e.g. tight coupling in that our app now relies on a base class etc.

But so far, I haven’t encountered any major problems, but remember I am working on a relatively small codebase and as a result not a large team.

Conclusion

I know there is an ongoing debate between Class vs Functional programming however, this blog does not focus on that or offer much philosophical direction towards deciding what’s best. It’s about showing one of the many possible ways to structure a redux application depending on the problem at hand.

As a developer, it’s up to you to figure out what works for you as I believe there is no one absolute “best way” to do things. There are quite a few factors involved e.g. business goals, team size, app functionality, personal preferences etc.

Disclaimer: I haven’t tested this on a massive production code base yet, but on my personal projects, but so far I am happy with it.

If you have any questions or anything needs clarification, you can book a time with me on https://mbele.io/mark

--

--