View, Action Creator, Reducer

This is one of the most common mistakes with Redux, especially if you’re just starting out. The pattern is the case where each container/view has a corresponding action and reducer. For example, look at the following example folder structure:

src

|--Containers

|--CartContainer.js

|--actions

|--cart.js

|--reducers

|--cart.js

In this example, the view/container has a corresponding set of actions and a reducer. What the developer is really doing is tying the view layer to the application state. The entire point of Redux is to create an app state separate from the view layer that you can access when you need to.

Why does this happen?

I think a big reason this happens is because of the Redux documentation. Honestly, I’m not trying to knock the docs because they are some of the best for any open source library I’ve ever seen; however, the examples in the docs lead to a train of thought that can be disastrous for real world applications. There are two examples in the docs, a todo list application and a Reddit application. The problem with these examples (as with most examples) is that they are very simple. There is only one set of actions and only one view. A developer can then make the leap (and it’s not a big leap) that a view needs a corresponding set of actions and a reducer. The concept applied to a real world app leads to an application that does not utilize the benefits of Redux.

How do I know when I’m falling into this pattern?

There are simple patterns that will trigger you. For example, in your view if you see:

// in your view

import * as cartActions from '../actions/cart';

You should see this and see a red flag. Why do you need all of your cart actions for this one view? You’re either not thinking in React or you are not appropriately slicing up your state. Try to rethink your actions and how they are organized.

// in your action

const actionTypes = {

ADD_CART_ITEM: 'ADD_CART_ITEM',

REMOVE_CART_ITEM: 'REMOVE_CART_ITEM',

CLEAR_CART: 'CLEAR_CART'

};

export { actionTypes }; // in your reducer

import { actionTypes } from '../actions/cart;

So here we have a reducer that is responding to all of the actions of the cart. This is also another common misconception. Sets of actions do not have to have a corresponding reducer and vice-versa. Note according to Dan Abramov (source https://github.com/pitzcarraldo/reduxible/issues/8):

The point of Flux and Redux is to make it easy to suddenly start reacting to the same action in a different place. You don’t know which actions you’ll need to handle in different places in advance.

We want to make sure that our reducers are flexible and can respond to actions from many different sets of action creators. When requirements for project change and you have flexible reducers, those changes will suddenly not seem so daunting.

A common middleware to use with Redux is redux-thunk. If you’re using thunk and find yourself using getState a lot you might want to rethink your actions. For example:

const myAction = () => (dispatch, getState) => {

const { cart: { cartItems } } = getState();

}

Sometimes your actions do in fact need to know about your application state. For example, if you need part of your state to make an API request. However, in my experience, most of the time the action is either calculating a new state for your reducer to handle or your state object is not sliced up correctly. Remember, it’s the job of the reducer to calculate a new state. The action should just provide the information necessary for the reducer to do its job. Fat actions can be another potential red-flag.

Looking out for these warning signs and taking appropriate action the moment you see them goes a long way to ensuring that you don’t end up with views and actions that know way too much about your application state.

Side Note: Please don’t misinterpret. I’m not saying that you should never use one of the above patterns. All I’m saying is that they should cause you to pause and think about what you’re doing. Assess whether or not, you are using Redux appropriately.

What does this lead to?

In summary, this pattern leads to a tightly coupled UI and application state. You can quickly get into patterns like this:

export const cartReducer = (state={}, action) => {

switch (action.type) {

case 'ADD_TO_CART':

case 'REMOVE_FROM_CART':

case 'CLEAR_CART':

return {...state, ...action.payload}

default:

return state;

}

}

In this situation, our actions know everything about our state object since they are just acting like setters. Our views are dispatching our actions, so consequentially our views know everything about our state. So let me save you some time, get rid of Redux and just use setState because you’re already doing it albeit a lot more boilerplate.

Note from the Redux docs:

Reducers specify how the application’s state changes in response to actions sent to the store. Remember that actions only describe what happened, but don’t describe how the application’s state changes.

So again, the docs are great, but you have to remember to take the examples in the docs for what they are, simple examples.

How do you avoid this pattern?

The same way you have to start thinking in React to be successful with React, you also have to start thinking about your application state to be successful with Redux. This means taking into account your entire application state from the get-go. Timeout. In theory, this sounds fine, but in practice requirements change, applications grow, and there are simply scenarios no human can 100% plan for. So how do you avoid this pattern. There are a few things that will go a long way:

Normalize your state. Taking out duplicate data goes a long way to helping you keep your application flexible. Keep your state shallow. For example for an e-commerce application, instead of having a cart reducer with cartItems in addition to coupon codes, have a cartItems reducer to manage that slice of your application state and a couponCodes reducer to manage that part of your state. Think in Redux. Remember, Redux is a powerful tool that gives you access to any part of your application state where you need it. Don’t fall into the pitfall of thinking that my CartContainer can only access actions and state from the cart slice of the state. Keep things more flexible. Naming slices of your application state appropriately is hugely important to remind yourself that Redux allows you to access any part of that state whenever and wherever you want. Read the Redux docs (including the FAQs). I cannot stress enough how good these docs are. The problem is you can’t just glance over them, you actually have to read them to understand the subtleties of creating an application with Redux.

Conclusion

The big problem with this pattern is that you can actually use it and build a lot with it before it becomes a huge problem. This patterns grinds your productivity to a screeching halt when requirements change and your application suddenly needs to dispatch actions and respond to slices of your state that break this pattern. That’s what makes this pattern so dangerous. It’s important to recognize the red-flags associated with this pattern before it turns into a daunting task to rework your application state.