A solution to Redux biggest pains

Gonzalo Aguirre
Underscope
Published in
6 min readJun 27, 2017

Redux has become the most adopted state management library, at least for those who use React. And that’s because it is great: it’s both declarative and super simple to understand, it’s really well documented and it has a lot of cool features thanks to the 3 principles it holds:

At Underscope we started using Redux quite long ago and we love the way it enforces you to think about the state and how it perfectly fits with React.

However, we’ve found two big pains that have been affecting us so far:

  • Boilerplate: for each new feature — even a tiny one — you need to create 2 or 3 new files (if you follow the idiomatic way).
  • Lack of Reusability: there is no easy way to reuse your Redux code.

The boilerplate is something you hate from the very beginning; it leads you to common mistakes due to the copy/paste and makes coding tedious.

Eventually, you’ll end up copy/pasting actions, types and reducers like a pro.

But the worst part comes up when you want to use a feature you developed in another project and you find yourself copying/pasting code over and over again. Your aim was to reuse the code you already wrote but you realize it’s not easy at all and it’s really frustrating.

The problem is that with Redux you think about the state as a single global object; therefore, it’s not easy to decouple something as a module.

Wouldn’t it be easier if we thought of the state as a big component that has recursively more components? Like React, yes!

Introducing Reduxable

Having suffered these problems for some time, we decided to do something with it and that’s how we ended up creating Reduxable. Next we’ll see a more detailed picture of the problems and the solutions we’ve found for them.

Reducing Boilerplate

When we discovered the boilerplate issue our first reaction was

Ok, Redux boilerplate is a trade-off for such cool other features it offers. We can live with that.

But it still felt wrong, we were not DRY. So we started digging into the problem and we found something interesting:

Actions and Types just exist because of the Reducer: it makes no sense to dispatch an Action with a Type if there is no Reducer for that.

This is a big clue! Maybe actions and types could be inferred from reducers.

Let me explain this with the well known Redux example: the counter

// reducers.js
import { INCREMENT, DECREMENT } from './types.js'
function counter(state = 0, action) {
switch (action.type) {
case INCREMENT:
return state + 1
case DECREMENT:
return state - 1
default:
return state
}
}
// actions.js
import { INCREMENT, DECREMENT } from './types.js'
export const increment = { type: INCREMENT }
export const decrement = { type: DECREMENT }
// types.js
export const INCREMENT = 'INCREMENT'
export const DECREMENT = 'DECREMENT'

It’s easy to see that the real logic of this code is in the reducers and that types and actions are just created to match those reducers.

If I needed to explain this code in words, it would be something like: this lets you increment or decrement a previous state, starting in zero.
Or in pseudo-code:

increment: (state = 0) => state + 1,
decrement: (state = 0) => state - 1,

So yes! There is really too much boilerplate: actions, types and a big switch.

The initial state and the reducers are just the two important concepts here. So that is what I would like to define.

Here follows what this would look like using Reduxable

import Reduxable from 'reduxable'const initialState = 0const reducers = {
increment: (state) => state + 1,
decrement: (state) => state - 1,
}
const counter = new Reduxable(initialState, reducers)

Ok, but what about the actions?

For each reducer a method with the same name will be created, and this method will dispatch the action for that reducer. You don’t have to worry about binding the dispatch; it’s already bound internally in each Reduxable.

Just call the counter reducers as methods.

counter.reducers.increment() // => dispatch({ type: 'increment' })
counter.reducers.decrement() // => dispatch({ type: 'decrement' })

And to access to the state?

Just call to .state

counter.state // => 0
counter.reducers.increment()
counter.state // => 1

Pretty simple, don’t you think?

With Reduxable you just write the minimal code you need: the initial state and the state reducers.

However, boilerplate is a problem already tackled by a bunch of libraries. So let’s go ahead and see how Reduxable can help with something more important.

Improving Reusability

One of the things that makes Redux awesome is the single source of truth. We all have learned that it has a lot of benefits, but when it comes to reusability it makes things harder.

The main problem is the potential collision between action types.
Suppose we want to create and share a Redux counter (yes, again a counter). To prevent these collisions we can add unique prefixes to them but we’ll end up having types like UNDERSCOPE_COUNTER_INCREMENT and that’s ugly.

But that’s ok, we don’t care too much about action names; we are strong; we can survive with these not-so-pretty names.

The real problem arises when you need two counters: you need to refactor your reducer! Or duplicate everything!

I wish it was a big lie, but it’s not. If you want an app with two counters, you have two options:

  • Either duplicate all the files and change the type, like INCREMENT_SECOND,
  • or refactor your counter reducer to receive something that identifies it and dispatch that identifier each time.

Both options seem a little bit complicated. The logic is the same, we want to increment/decrement a counter, but we need to do extra work. In addition, your code becomes more complex. No one likes that.

In other words, what we’re doing is to scope the different counters, in the first case adding a prefix/suffix to the action type, in the second one passing an id into the action. And we need to do that because we share the same scope for everything.

What if we could standardize the way we scope reducers? What if someone could do that for us?

Well, that’s what Reduxable does: it assigns a scope to each reduxable. And that’s easy to do because we already have an implicit scope! Just take a look at this…

{
counters: {
counterA: new Counter(),
counterB: new Counter(),
}
}

Given the state is a plain JS object we can trivially define the scope as the path to each reduxable:
- the scope for counterA is counters.counterA
- the scope for counterB is counters.counterB

By adding that scope in each action and checking for it in the reducer we can avoid a collision between actions.

counterA.increment() 
// will dispatch { type: 'increment', scope: 'counters.counterA' }
counterB.increment()
// will dispatch { type: 'increment', scope: 'counters.counterB' }

Internally counterA and counterB reducers will be applied only if they match the type and the scope. In this case, the type is identical but the scope is not; thus, they won’t collide.

Ok, but how can I reuse one of these?

You have to create a class extending from Reduxable. As React developers we choose classes because that’s something we are all used to.

import Reduxable from 'reduxable'class Counter extends Reduxable {
constructor() {
super(0)
}
}
Counter.reducers = {
increment: (state) => state + 1,
decrement: (state) => state - 1,
}
export default Counter

Now that you know why to use it, let’s see how to use it in your app!

Integrating Reduxable with Redux

Using Reduxable in your Redux app is easy. All you have to do is to replace the combineReducers and createStore imports to get them from reduxable instead of redux.

Then you can combine both reduxables and redux traditional reducers.

import Reduxable, { combineReducers, createStore } from 'reduxable'
import Counter from './reduxables/counter'
import counter from './reducers/counter'
const store = createStore(combineReducers({
reduxableCounter: new Counter(),
reduxCounter: counter,
...
}))

Or if you want to go all in you can combine reduxables and traditional redux reducers using theReduxable class and then create the store based on it.

import Reduxable, { combineReducers, createStore } from 'reduxable'
import Counter from './reduxables/counter'
import counter from './reducers/counter'
const myApp = new Reduxable({
reduxableCounter: new Counter(),
reduxCounter: counter,
...
})
const store = createStore(myApp)

For more details visit the documentation.

Conclusion

Redux itself presents some issues we all end up solving by ourselves. But some of these issues can be tackled in a standard way, making it possible for a library to do that.

Reduxable tries to fill the gap between Redux simplicity — to understand what a reducer does— and complexity — when writing the code around that reducer.

Please share your thoughts in the comments and recommend if you like it.

--

--