Scaling Redux part 3: Reducing Boilerplate and other stories

Uttam Kini
Aug 22, 2017 · 3 min read

In Scaling Redux part 1 and 2, I talked about how codebase and state architecture impacts complex applications. This post is about some other common problems that you encounter when your Redux codebase grows.

Eliminate Switch Case Fatigue

Redux’s benefits of one way data flow, time travelling debugging etc come at a cost of more abstractions (to remember and type). One common example is the oft repeated switch case statements in reducers which look like the following

const reducer = function(state, action) {
state = state || defaultState;
switch (action.type) {
case ACTION_1:
return {... state, {"foo": "bar"}};
case ACTION_2:
return return {... state, {"zoo": "zaz"}}

...
...
default:
return state;
}

The switch case statement is a hangover of sample code and tutorial apps. Though easy to read at first glance, it is a line too many to type and by extension mistype. Mistypes can create subtle defects that take a long time to debug.

If we write a wrapper reducer function like

const declarativeReducer = (defaultState, config) => {
return (state, action) => {
state = state || defaultState;
action = action || {type: null};

const handler = config[action.type] || (() => (state));
return handler(state, action.payload);
};
};

then the above reducer becomes,

const reducer = declarativeReducer(defaultState, {
[ACTION_1]: (state, payload) => {... state, {"foo":"bar"}},
[ACTION_2]: (state, payload) => {... state, {"zoo": "zaz"}}
});

As you can see the reducer is now declarative and you just have to type in only as much as it is really required to handle the action.

Namespace your Action Types

As you grow your codebase, you will have a lot of actions and action types. There is a chance that you may have duplicate action names. This can be quite disastrous and can get your app into an inconsistent state if a wrong subtree is updated unintentionally. The solution to this is quite simple though. Namespace your action types with the ‘Ducks’ that they reside in. for example for an action to Fetch orders that lies in data/OrderDuck.js , name it DATA_ORDER_FETCH_ORDER . You can choose your own convention, as long as it is hard to duplicate action types.

Have a large number of small Reducers/Ducks

One other anti-pattern that I encountered was the God Reducer/God Duck akin to the God Object pattern. A lot of actions and reducer logic starts going into one file or abstraction. To overcome this, make sure that you keep your Ducks as small as possible. One smell that you haven’t done this enough is when you haven’t used combineReducers a lot in your codebase. If you follow the directory and state structure as described in Scaling Redux part 1 and 2, then it is quite intuitive to have small reducers composed in hierarchy like the following,

const appReducer = combineReducers({
data: dataReducer,
ui: uiReducer
});
const uiReducer = combineReducers({
headerReducer: HeaderReducer,
bodyReducer: BodyReducer,
sideBarReducer: SideBarReducer
});
const dataReducer = combineReducers({
orderDataReducer: orderDataReducer,
userDataReducer: userDataReducer,
accountDataReducer: accountDataReducer
});

Resetting State is as important as setting it

When you start programming in Redux,resetting state is something you don’t worry about too much. However, interesting bugs will show up if you don’t reset your state well after significant actions like a route transition. It is usually UI state that you want to reset (you don’t want a dropdown expanded when a user comes to a new page). If you have separated UI state from the data state as described in Scaling redux part 2, then it is quite easy to reset UI state. You just have write a wrapper function/reducer over an existing UI subtree reducer like the following.

const resettableReducer = (targetReducer, resetActionType) => {    return (state, action) => {
state = action.type === resetActionType ? undefined : state;
const nextState = targetReducer(state, action);
return nextState;
};
};
/* Usage */
resettableReducer(reducerWithRouteState,ROUTE_TRANSITION_ACTION)

In the above example, whenever a `ROUTE_TRANSITION_ACTION` happens, all the state in `reducerWithRouteState` will be reset to the default state. If you have mixed your UI and data state trees, resetting state will get messy.

That ends this post and mostly probably the series on Scaling Redux(unless I remember more stuff). My biggest takeaway after writing these posts, is that with complexity moving towards the front end, just using a framework is not enough. You need to have a solid technical architecture under the hood.

)

Uttam Kini

Written by

Solver of problems concerning people and software, compulsive wikipedia reader

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade