How to extract logic from one Redux reducer to another using appendReducer

Anirudh Eka
3 min readMay 2, 2018

If the history of our planet was scaled to 24 hours, the entire existence of our species would be less than 2 minutes, and the existence of Redux would probably be a fraction of a millisecond.

And yet…it’s still old enough to have legacy code written in it.

Have you ever faced a giant reducer that you wanted to break into smaller chunks?

Ideally you would use combineReducers to build your reducer from other reducers that handle the subtrees of the state. For example if you had a reducer called the historyOfEarthReducer and it operated on a state like the following:

{
jurassicEra: //...
riseOfTheMongols: //...
afterAbramov: //...
}

You could could compose historyOfEarthReducer with reducers responsible for each subtree of history:

let reducers = {
jurassicEra: jurassicEraReducer,
riseOfTheMongols: riseOfTheMongolsReducer,
afterAbramov: afterAbramovReducer
};

const reducer = combineReducers(reducers);

But what if you or your ancestors didn’t think about making composable code way back when you started writing this reducer (after all who had the brain cells to think of that when we were algae)? So you had one big monolith historyOfEarthReducer. But then you discovered fire, the wheel, and decided it’s time break things up.

But unfortunately most of the logic of your reducer is incredibly messy and interdependent. What if the only part of the reducer logic that is relatively painless to extract is afterAbramov?

The limitation with combineReducer is it can only work if you have a reducer for each sibling of the tree. But you prefer the main reducer to continue to handle the reducing logic for jurassicEra, riseOfTheMongols, and whatever else; while you build out afterAbramovReducer.

{
jurassicEra: //...handled by historyOfEarthReducer
riseOfTheMongols: //...handled by historyOfEarthReducer
afterAbramov: //...handled by afterAbramovReducer
}

AppendReducer

What you really want is not to combine smaller reducers to make a big one, but to append a small one to an already big one. Recently I faced such a situation in a codebase I was looking at.

In order to solve this problem I created something called appendReducer that’s used like this:

let reducersToAppend = {
afterAbramov: afterAbramovReducer
}
const appendReducer(historyOfEarthReducer, reducersToAppend);

appendReducer takes as its first argument the core/main reducer that is responsible for most of the keys in the tree, historyOfEarthReducer in this case.

Its second argument (similar to combineReducers) is an object mapping keys to reducer functions. The key should refer to the subtree that the main reducer wants to delegate and the value should be the reducer it wants to delegate to. So here it would be delegating management of all state under the afterAbramov key to afterAbramovReducer.

This is how I implemented appendReducer:

I’m using Ramda’s zipObj a cool functional programming library, but it’s not necessary for implementation.

In the wild

In our project we used appendReducer along with common generic reducers to incrementally reduce duplication in our code.

How did you solve this problem?

I’m pretty new to Redux and am always looking for ways to improve. Definitely leave a comment if you found other/better ways to solve this! Also if there’s a better way to implement appendReducer, let me know!

--

--

Anirudh Eka

Passionate about finding patterns in computers, society and minds. Follow on twitter for free links! https://toomanynames.com/