Charles Stover

@Charles_Stover

Replacing Redux with ReactN to reduce complexity and bundle size

Following my article on an intuitive, boilerplate-free global state solution for React, I decided to put real data behind the concept. I started with a bare-bones example of a React application that harnesses Redux for global state management, and I converted it to ReactN. During this process, I measured objective changes in memory allocation and bundle size. A thorough analysis and source code is provided on GitHub, but I will summarize here, as well as walk through how easy a conversion is and how much simpler the refactored code becomes.

You may use ReactN yourself via reactn on NPM or contribute to, fork, or otherwise spy on the open-source GitHub repository.

The Application 0️⃣

The application is a showcase of global state features. On mount, the application fetches a page and stores the response in the global state. On render, a button displays a number from the global state. When the button is pressed, the number increments.

This isn’t breath-taking functionality. This is a demonstration of the three most important aspects of global state: the ability to read from it, the ability to write to it, and the ability to manage it asynchronously.

There are four parts to establishing a global state in React, and we will walk through each of them: the dependencies, initializing the global state, connecting the components to the state, and reading from and writing to the global state.

The Redux Application 1️⃣

The Dependencies 👶👶👶

We need three packages to establish our Redux application: redux, which contains the logic to create and interact with the global state; react-redux, which contains React logic and higher-order components for integrating the global state with a React application; and redux-thunk, a Redux middleware for processing asynchronous state modifications.

Initializing the Global State 🎬

To initialize the global state, we use the createStore function from the redux package. To it, we pass the desired reducers and middleware.

The end result looks like this:

The createStore function is quite the enigma, but I won’t spend this article discussing common developer complaints about Redux’s learning curve!

Connecting the Components 🚈

In order to connect the components to the state, we must wrap the application in a global state context provider, then wrap each connected component in a higher-order-component that behaves as a global state context consumer.

The end result looks like this:

That’s a lot of code just to use global state. Despite the react-redux package being geared towards integrating Redux with React, I personally do not feel the above has a React-first approach in mind. But I won’t spend this article discussing common developer complaints about Redux’s boilerplate!

While this step includes the mysterious actions fetchData and incrementX, I included them in the next section, as they are directly related to writing to the global state.

Reading from and Writing to the Global State 👁‍🗨

With all of that boilerplate out of the way, we can finally use the global state! We access and modify the state through the connected component’s props.

The end result looks like this:

Actions aside, the component itself is very easy to read!

The ReactN Application 2️⃣

Now that we’ve seen the familiar Redux implementation, let’s see how we can decrease the complexity above and turn it into a more readable, more intuitive React application.

The D̶e̶p̶e̶n̶d̶e̶n̶c̶i̶e̶s̶ Dependency 👶

Install the reactn package. There is no asynchronous middleware. ReactN supports Promises out-of-the-box. You can remove the three aforementioned Redux dependencies and sigh in relief as your application drops by 213 dependency files and has a 13% smaller production bundle size when it uses ReactN instead.

Initializing the Global State 🎬

The first of the cumbersome boilerplate, time to initialize the store and establish the reducers. Reducers and actions are optional in ReactN. I only include them here for a fair comparison, as we are using them in Redux. If you find reducers to be too much boilerplate, you are more than welcome to abstain from using them. A reducer-free alternative is provided later.

We use setGlobal to literally just set the global state. Great for initializing.

We use addReducer to add a reducer. Note that ReactN does not use actions. We call the reducer directly!

We’ve reduced the boilerplate from 640 bytes to 330 while including actions! More on that later — for this project, I wouldn’t create a fetchData reducer at all. I only include it here for symmetry with the Redux application.

Connecting the Components 🚈

ReactN does not use higher-order components. To connect your components to the global state, you add one byte to your file.

I added an n to the end of the package name, changing react to reactn. That was it. Your Components and PureComponents now have access to the global state.

That was 728 less bytes of boilerplate! One less app-wide HOC and one less HOC per component.

Reading from and Writing to the Global State 👁‍🗨

How do we use the global state if we didn’t tell it what props we want on our component? With React in mind, ReactN treats the global state as a member variable — exactly as you are used to using with local state’s this.state.

The component looks exactly the same — except to access the global state, you use the this.global member variable instead of props. No higher-order component required!

That’s it! The component is done. It’s fully functional.

fetchData 🐶

I mentioned how I would handle the fetch data action differently with ReactN, so I will elaborate here. ReactN is not limited to actions and reducers, like Redux. ReactN’s global state member variable is meant to behave exactly like React’s innate local state member variable, and that includes an analogous this.setGlobal method that behaves like this.setState. The setGlobal method has additional functionality of supporting JavaScript Promises.

Unless fetchData is being called across multiple components, it doesn’t afford us much to name it as a reducer, so I would simply call setGlobal instead:

Fetch index.html, parse the text, create the state object, and set it.

I wouldn’t be opposed to the incrementX reducer simply being a method on the component that calls setGlobal either, since it isn’t being shared across components either.

One Last Comparison 🏁

I wanted to show the two applications, side-by-side, in their completed state, since the above really only covers snippets. The code below would be better served if it were split into multiple files, but the real issue is reading, writing, comprehending, and maintaining this code. This applies not only to you, the reader, but your team, which is often comprised of junior developers.

Before: index.js with Redux 🔴

After: index.js with ReactN 💚

Before: App.js with Redux 🔴

After: App.js with ReactN 💚

The Cold, Hard Fact ⛄

Whether you believe one method is easier to read than another, one thing here is objective. The above application in a production build is 531,736 bytes using ReactN. It’s Redux counterpart is 611,990 bytes — a 15% increase.

The difference likely drops with scope, as a project re-uses more actions, and contains significantly more logic unrelated to global state. However, with an increase in scope, you see an even greater reduction in boilerplate!

Conclusion 🔚

If you want to contribute to this project, it is open-source on GitHub, and I would be absolutely ecstatic for more community feedback. If you want to play around with this project, simply npm install reactn --save or yarn add reactn.

If you liked this article, feel free to give it a clap or two. It’s quick, it’s easy, and it’s free! If you have any questions or relevant great advice, please leave them in the comments below.

To read more of my columns, you may follow me on LinkedIn and Twitter, or check out my portfolio on CharlesStover.com.

More by Charles Stover

Topics of interest

More Related Stories