The Secret to Using Redux at Scale

Unlock a tool you never knew was there.

Every time I see a talk or watch a video on Redux or what kind of state to use in React, it’s the same-old talk about when to use component state, the new Context API, or Redux.

I think people are missing something, I really do. That’s where createNamespaceReducer comes in. Why choose which of 3 completely different state systems to use when you could always use Redux?

This article isn’t specifically about doing all your state in Redux, it’s about using createNamespaceReducer to assist in putting all of your state in Redux.

I see a lot of benefits from component state, but I don’t like having to deal with this or use JavaScript classes in general (explanation coming in another article).

When building data-driven apps with Redux, you’ll quickly find reducers are limited in their capabilities, and it can be tough to choose how many reducers you want to create for each section of state. The last thing you want is to couple your component structure with your reducers. I created createNamespaceReducer to solve those problems.

Disclaimer: Why would you put everything in Redux? Only if it solves your problems. I’m not saying you should be doing it this way, I’m just giving an option for people who do.

Separating Logic

I see a huge separation in business logic and view logic. The whole point of Redux is to actually develop a “Redux app” independent of the underlying view-model.

Straight from the docs (with a bit of interpretation):

Redux can be used as a data store for any UI layer. The most common usage is with React and React Native, but there are bindings available for Angular, Angular 2, Vue, Mithril, and more. Redux simply provides a subscription mechanism which can be used by any other code. That said, it is most useful when combined with a declarative view implementation that can infer the UI updates from the state changes, such as React or one of the similar libraries available.

It’s not explicitly stated, but it’s implied. If you have a data store layer that works separately from your UI layer, is your app really React or is it Redux?

For instance, I have to work on a project where logic is shared among all company projects. All this logic is in Redux, but the apps might be using legacy AngularJS, React, or both. I needed way to unite these structures among many applications, and neither React’s component state nor the Context API will work for us.

There are other benefits to doing everything with Redux. Since Redux handles all the state mutations, my business logic can be made up of pure stateless functions which makes unit testing super simple. These types of functions are easy to reason about.

It’s also far easier to unit test functions instead of React components. You can also unit test simple reducers much easier than complicated ones. createNamespaceReducer removes a lot of the need for complex reducers especially when paired with an async middleware library like Redux-Observable, Redux-Thunk, or Redux-Saga.

Another benefit is debugging. Redux has some awesome devtools to figure out what’s changed, what’s in state, and generally what’s happening to your app. You also have the ability to go forward and back in time and dispatch your own actions to see what happens. Neither React’s component state nor the Context API have anything like this.

To top it off, I only have to train coworkers on one way to mutate state, and I also know that there’s only one way they would. It simplifies a lot of things because I can write a single bit of app logic that everyone can reuse in all of our apps. It’s the same action creator and the same way to execute it, it’s the same way to debug and the same place to look for issues.

The only drawback from what I’ve seen is when when you have to explain to people versed in React component state. To me, createNamespaceReducer opens up tons of possibilities, but if you’re doing all your state in Redux, it can feel limiting for some because it takes away a tool they’re used to using. Think about it in the same way how functional programming works vs object-oriented. It’s pretty limiting your first time around when you can’t set function arguments and object properties to whatever value you want.

If what I’m saying sounds unusual or outlandish, it’s the same kind of thing you’ll see in the Redux docs:

It helps you write applications that behave consistently, run in different environments (client, server, and native), and are easy to test. On top of that, it provides a great developer experience, such as live code editing combined with a time traveling debugger.

So what did I do?

Unlocking the Power

The most-important first-step is learning how createNamespaceReducer works:

createNamespaceReducer(

reducer,

initialNamespacedState,

)

But how do you use it for real?

First, write a simple reducer:

This is probably something you’ve written before. It’s only concerned with one thing: is the app loaded or is it loading?

Next, we setup our loading state actions:

This is as simple as it gets. We’ve created a finite state machine for our application’s loading state.

Problem is, there’s only one loading state for the whole app. If we want to use this reducer with other components in other parts of the app, we’ll have to write it again and again. I’ve even seen projects where isLoading is a prop on just about every reducer! That means you’ll see copy-paste logic everywhere.

That’s where the power of createNamespaceReducer comes into play. It prevents you from rewriting the same logic all over the place.

Here’s an example using the same loading state reducer and actions we had before:

Actions now need to have a namespace prop; otherwise, your namespace reducer won’t know how to categorize the state you’re changing. On the other hand, your reducer would only need one change: wrapping it in createNamespaceReducer .

There’s one other important change you’ll need in your reducer to prevent memory leaks. Notice how our reducer is using initialState instead of false for the LOADED state? In this particular reducer, initialState and false are exactly the same, but in other reducers, that might not be the case. For instance, if your initial state is an object or array, you will need to revert back to initialState for your namespaced reducer to remove it from the underlying state object.

Another thing to note is how I’ve exported the reducer separately from the namespaced reducer. If you export only the namespaced reducer, then your unit tests have to test the namespaced reducer as well as isLoadingReducer . A best practice for unit tests is to never unit test someone else’s library when all you want to test is your own functionality so keep those separated.

Be aware, your namespaced reducer always returns its previous state unless you pass in a namespace prop; therefore, passing a namespace of undefined returns the previous state no matter the action type.

How it Works

To understand how createNamespaceReducer works, you need to first understand how your state looks when using it.

Before:

After:

Pretty simple. It let’s you tie a namespace to a segment of state. And it really works as if it’s a reducer itself. It compares the previous and next states and chooses to return a new or the same namespaced object just like you’d normally do in a reducer.

Under the hood, it’s really just an object with some helpful logic around it, but it could be anything else like a Map or an immutable object. In fact, I’ve got a createMappedNamespaceReducer which I use in Node.js because I can namespace using the WebSocket connection object reference instead of having to come up with a random string for each WebSocket connection.

You can even compose your own createNamespaceReducer using a helper utility createNamespaceReducerCreator . I’m not going to go into how you’d do it, but there are quite a few examples in the GitHub project.

Updating State from REST Calls

What if you want to update a list of items, essentially a payload, when you get something back from a REST call? Instead of creating a separate reducer for each thing you’re calling out, why not have a super simple reducer that takes a payload and literally puts that into state?

This is what it looks like:

This is actually fairly similar to what we ended up using recently in a data-driven project that hooked into a REST API. It helped us quickly develop the app and worry about all the AJAX calls in our async middleware.

We were able to push this even farther:

Just so it’s clear, I significantly reduced the epic’s logic. It’s definitely missing a bunch of stuff. If you want to know more, checkout my article: Redux-Observable Can Solve Your State Problems.

We have a single action specifically for fetching list items and that’s it. There are no store and reset actions because our payload reducer handles it. All we have to do is have our fetch action call a storePayload action when it’s done with the correct namespace, and we’re good to go!

The only reason we have a fetchListItems action rather than fetchPayload is because this epic does not need to fire in the same way for other types of data. If I showed more about how the actual epic looks, it might handle errors differently or set the loading state before triggering the AJAX call. It might also format the returned data differently. Point is, we can use actions to change up the state before it eventually gets put into a generic Redux state container.

When dealing with WebSocket connections where I want to do more complex-syncing logic, I write more complicated reducers. Because of this, having createNamespaceReducer available simplifies the reducer to a readable state and reduces a lot of copy-paste logic for each data type.

Selecting from Namespaced State

If you’re familiar with my ReduxConnection component, this is what it looks like when used with payloadReducer :

createNamespaceSelector provides a mechanism for grabbing the item by its namespace. Usually you’d get back an object with a prop payload , but now you can get that prop with the same name as your namespace.

If you want to know more about ReduxConnection and selectors, checkout my article: Why you shouldn’t need `connect` from React-Redux.

Drawbacks

I always make sure to note all the drawbacks to using a certain system I’ve made. I think the tradeoff using a namespace system outweighs creating separate reducers.

We lose some of our debugging. Because this is an unusual way to do Redux state, the Redux devtools don’t know what a namespace is and don’t reveal it unless you click on an action. It means you have to go through a number of very-similarly-named actions to try and find the one you care about. This is a major drawback for me, but hasn’t been a huge deal. I’ve made a few named reducers, even if similar, to give that little bit of separation especially when storing different types of data.

What about namespace collisions? That’s a great question! It hasn’t been an issue in the last 9 months we’ve been using it, but I could see where it could come up. Our process is to tie namespaces 1:1 with a component’s name if it’s a component-specific action, and if we’re using data to control the loading or payload state, we pass in a semantic name for the data we’re grabbing such as 'permissions' or 'accessToken' .

Two components could be named the same, but again, it’s not come up. Solving a problem like this is actually easier than you think because you know both the namespace and the action creator. It’s simple enough to do a search on either and come up with a pretty good understanding of where a namespace collision might be causing problems.

Unlock the Power in Your Own Apps

The code ended up being more complex than I planned; definitely not something you’d want to write yourself so I created a few utility functions with unit tests and open sourced it for everyone to use.

You can find them here:

More Reads

If you like what you read, you should also checkout my other articles; especially anything about Redux: