Building Modular Redux Applications

A new way to organize your Redux applications

TL;DR

Check out our new library redux-dynamic-modules, which provides a better way to organize your Redux application.

You can also see an example of how to use the library for code-splitting in this post written by the co-author of this library, Navneet Gupta:.

Redux At Scale

Redux is a simple, incredible library that makes it easy to write straightforward and testable state management code. On Azure DevOps, we wanted to modernize how we approached state management by introducing Redux into our system. However, we quickly identified two classes of problems that occur when using Redux at a large scale.

Organization

A typical Redux application requires cooperation between a collection of small pieces:

The actions

A reducer

The selector functions

Optionally, middleware like redux-saga or redux-observable

Redux only works well if all of these pieces are present, but it can be difficult to register all of them! Consider the following example that uses redux-saga :

Here, I have three separate locations where I need to configure the related Redux pieces. If I want to add additional functionality, I need to make sure I register all the pieces in the right locations. At a large scale, this becomes unmanageable.

Code Splitting

As seen above, Redux requires you to define everything up-front. Any reducer that I might want to use later in my application has to be added to the store in the initial store creation call.

Instead, I want to be able to add/remove reducers at runtime. For example, if the user clicks a button to open a rarely used dialog, I want to load the scripts for that dialog and add dialogReducer at that time. The basic Redux store does not provide a mechanism for this behavior.

Further, even if we can devise a way to load reducers dynamically, we will still have to devise separate ways to load the other constructs like sagas or middleware dynamically, because they are all registered in different manners.

Enter Modules

We can solve both of the problems listed above by introducing a new concept: the module.

A module is a grouping of all of the pieces that make Redux work. Consider the following module definition:

An example TodoModule

Here, in one location, we have grouped together all of the small pieces associated with the TodoState . This is now easy to import, register, and re-use.

Now, creating our store can look like this:

Creating a module store

Now, there is one location to add additional functionality to the store, as opposed to the three locations from before.

Dynamic Modules

We also want modules to be added and removed from the store at anytime. To do this, we can define an addModule function on the store that accepts a module and registers it with the store. When a module is added to the store, all of its contents are added to the store. This means:

Any reducers in the module are added to the store and can act upon actions that are dispatched

Any middleware in the module are added to the Redux middleware pipeline

Any sagas in the module are executed with the redux-saga middleware.

The added module can be removed by calling the function returned by addModule .

In this example, we can add the debugger module, only if the code is in debug mode:

Dynamic Module Loader

Finally, for better usage with React, we can define a DynamicModuleLoader React component, which can take modules as props and load/unload them on component mount/unmount. For example, if we wanted to load the dialogModule when the user opens a dialog, we could do the following:

Now, when the user launches the Dialog, the dialogModule is added, which means all of the reducers and middleware the module includes will be added to the store. When the user closes the dialog, the dialogModule and its contents are removed.

With the help of the DynamicModuleLoader component, we can use popular libraries like react-loadable to perform code-splitting for both our React components and our Redux constructs!

redux-dynamic-modules

All of the above functionality has been implemented and published as an NPM package: redux-dynamic-modules . We have been using it in production in Azure DevOps with great results.

It solved our code splitting issues with Redux. With DynamicModuleLoader , it becomes trivial to load less important functionality and state after the primary page content loads. For a great example on code-splitting with this library, see this companion post.

, it becomes trivial to load less important functionality and state after the primary page content loads. For a great example on code-splitting with this library, see this companion post. It has become very easy to share state across our product. We can write generic modules, such as a CurrentUserModule , for example, and separate areas of the product just need to import and include it in their store.

, for example, and separate areas of the product just need to import and include it in their store. We have found it makes Redux easier to use for newer developers because all of the small pieces are joined together into one logical unit.

Check it out on Github and let us know what you think!