We have seen the frontend part of apps becoming more complex in recent years. This is especially visible in single-page applications where a lot is happening, even in terms of data manipulation, what’s typically a backend domain. Because of this, a few approaches have appeared for how to store, manipulate, and generally manage the frontend data. One of the most popular libraries for this purpose is Redux.

What is Redux?

As the official site says, Redux is a predictable state container for JavaScript apps. It creates a single source of truth for data in an entire application—one central state store for all purposes. The library is very flexible, so it works in every JS environment and, no matter where it’s used, it behaves the same. Redux also has a lot of flexibility thanks to the middleware system which makes it possible to add functionalities.

It’s also worth adding that Redux is often affiliated with React. It’s widely used in React-based apps, but it’s not made specifically for it. 

Redux architecture

Redux Architecure

Centralized store

As I wrote before, Redux has a centralized architecture. It means that we have a single store for the whole app and every part of it can access (and modify) any data. That provides us with a single source of truth which is one of the three fundamental principles of Redux. To use the store, we can use the following methods:

  • getState() – is used to access current data in a state
  • dispatch(action) – is used to modify data by sending an action description to the store
  • subscribe(listener) – allows registering an event listener, which is called whenever the state is changed.

Some of you who have used Redux before may not recognize it. That’s why, most of the time, we use Redux through the react-redux wrapper, which offers its ways of accessing the store which are more suitable to the React ecosystem.

Reducers

Reducers are functions that describe how the state should be modified as a result of the action. There is just one reducer associated with the store but multiple reducers can be combined into one. Let’s now get into the details about why there are reducers and what actions are. Action is a definition of what the user wants to change in a state. It consists of an action type (obligatory field) and an optional payload used during action execution. From any part of the app, we don’t change anything directly, and neither action does that directly. A sample action can look as follows:

{
    type: 'ADD_TAG',
    value: 'development'
}

Because the action itself does not change anything, we have reducers which do that. A reducer is a function that takes an action and the current state and returns the new state. It’s as simple as that. It’s worth remembering that it returns a new state and does not modify the current one. It’s effortless to spot a reducer in the application – it’s usually a huge switch case statement. A sample reducer may look like this:

function tagsReducer(state = [], action) {
    switch (action.type) {
        case 'ADD_TAG':
            return [
                ...state,
                action.value
            ];
        case 'REMOVE_TAG':
            return state.filter(x => x !== action.value);
        default:
            return state;
    }
}

There are a few things worth mentioning:

  • As previously mentioned, we are not modifying the current state, but instead creating a new one with appropriately modified values
  • There are no side-effects. The function does just one thing
  • Reducers should always behave the same for the same input
  • There must be a default case, which returns the state as it is. That’s because every action goes through a reducer no matter whether it’s known or not, and the store depends on what reducer returns
  • Everything is done synchronously

Everything that we’ve pointed out here summarizes the other two fundamental principles of Redux: read-only state and changes made only with pure functions.

Middlewares

Middlewares are the last part of Redux architecture. They are extensions that are run between action dispatch and provision to the reducer. They enable us to extend Redux with more functionalities. The most popular cases for middlewares are:

Of course, middleware can do more, and you can easily do your own. I can provide here an example of one of the projects I participated in, during which we created a middleware to synchronize Redux state with C# backend in real-time via SignalR.

Advantages and disadvantages of Redux

I have used Redux and alternative technologies in numerous commercial and self-development projects. Based on this, I’d like to sum up some of the advantages and disadvantages I’ve encountered while using it.

Advantages

  • Small size – 7.95 kB minified and 2.83 kB gzipped.
  • Popular – nearly 5 million weekly downloads on NPM, used by over 1 million projects on Github where it also has over 53,000 stars.
  • Data flows are very transparent – it’s easy to find out what’s happening and where, since every action goes into one place, the dispatch function.
  • Based on basic JavaScript functionality, so it works in every environment.
  • Easy debugging, especially with a dedicated browser extension.
  • Easy to bind with popular UI frameworks with dedicated libraries like react-redux, angular-redux, or polymer-redux.
  • Enforces clean architecture with a single source of truth.

Disadvantages

  • In the beginning, it may seem hard and not that obvious. Fortunately, while it’s hard to learn, it’s easy to master.
  • It requires writing a lot of code at the beginning that may seem unnecessary.
  • Supports only simple types and basic data structures; therefore, it’s not possible to store complex objects in Redux.
  • Despite having TypeScript support, it’s not always easy to do proper types and code suggestions. Fortunately, there are libraries like typesafe-actions that provide easy-to-use type helpers.

Should I use Redux?

That’s a fundamental question you should ask when creating a new project or before adding any state management library. You can ask yourself a few questions to determine whether you need it:

What is the planned size of your application? How many different areas will it have?

Of course, you can use Redux in an application of any size, but it may not always be the best solution. In small apps, it may seem lie “too much”. 

What kind of data do you need to keep? Global application state or just component-related information?

While it’s tempting to put everything in Redux, it’s not worth putting information like “is save modal visible”. It should instead be used for user inputs or backend data used through-out the app

How much data needs to be processed inside the app?

It’s very often connected to the application size. If there isn’t much data that is not related to specific components, maybe it’s not worth using Redux.

Would built-in UI libraries like React’s Context be sufficient for your use cases?

React Context has become so easy to use (especially with useContext hook), that you should consider it instead of Redux for passing data in components hierarchy. Also, you may take a look into other means like useReducer, which provides Redux-like experience in React.

Are you sure you are able to implement Redux in your app correctly?

What are alternatives to Redux?

There are many libraries and approaches to state management. It’s worth checking them because they may be a better fit for your project—one which plays better with your code style or just gives you broader horizons. Here is the list of a few approaches and libraries that you may want to check:

  • React Context – an approach built into React for managing data flows inside components hierarchy.
  • Angular services – the default approach in Angular for managing states. If you use Angular, there is no chance you haven’t heard about it.
  • Flux architecture – application architecture made by Facebook for controlled state management on the frontend. Redux is based on it but is not a direct implementation of Flux (there are a few significant differences).
  • MobX – a state management library that depends on observing changes of variables values, definitely more flexible than Redux.
  • mobx-state-tree – another state management library. As the name states, it’s based on MobX, but enforces an architecture and way of use that is similar to Redux.
  • Vuex – a state management library made for Vue, very similar to Redux.
  • NuclearJS – an implementation of Flux architecture; unfortunately, it’s no longer maintained.

Final words

Redux is a very nice library for JavaScript state management. It’s small, popular, and while it’s perhaps not the most natural to use (at least at the beginning), it provides clean architecture, modularity, and excellent community support—which often may be more vital than everyday simplicity. It’s not the best fit for every project but, when it is, it will surely be beneficial.