Why it is Better to spread the Reducer in Files

One way is Single File Reducer among multiple ways to arrange redux actions, action creators & reducers. Here's why you should break it down.

Why it is Better to spread the Reducer in Files

Redux is well established as a large-scale state management solution for React Applications.

Though there could be multiple ways to arrange your redux actions, action creators, and reducers.

One common way I saw is having everything in a single file in my current application at work.

// reducer/app.js

export const PAGE_LOADED = `PAGE_LOADED`
export const ITEM_DETAIL_REQUESTED = `ITEM_DETAIL_REQUESTED`
export const ITEM_DETAIL_REQUEST_FAILED = `ITEM_DETAIL_REQUEST_FAILED`
export const ITEM_DETAIL_LOADED = `ITEM_DETAIL_LOADED`

const INITIAL_STATE = {
  page: null,
  items: [],
  errors: [],
  item: null
}

export const actionFactory = (type) => (payload) => ({ type, payload });

export const pageLoaded = actionFactory(PAGE_LOADED);
export const itemDetailLoaded = actionFactory(ITEM_DETAIL_LOADED);
export const itemDetailLoadingFailed = actionFactory(ITEM_DETAIL_REQUEST_FAILED);

export const loadItemDetail = params => dispatch => 
  Promise.resolve()
    .then(() => fetch(`/items/${params.id}/`))
    .then((res) => res.json())
    .then(data => dispatch(itemDetailLoaded(data)))
    .catch(err => dispatch(itemDetailLoadingFailed(err)))
	
const reducer = (state = INITIAL_STATE, action) => {
  switch(action.type) {
    case PAGE_LOADED:
      return {
        ...state
      }
    default:
      return state;
  }
}
export default reducer;

As the above reducer already looks cluttered, imagine this with 10-15 different actions. With those actions, there will be action creators. The reducer will also grow to respond to those actions.


As a solution, breaking down your reducer into multiple files will always be a good idea. One such arrangement will be the following things in different files:

  • Actions
  • Action Creators
  • Reducer

Let's split the above reducer into Files as per the above arrangement:

Actions

// reducers/app/actions.js

export const PAGE_LOADED = `PAGE_LOADED`
export const ITEM_DETAIL_REQUESTED = `ITEM_DETAIL_REQUESTED`
export const ITEM_DETAIL_REQUEST_FAILED = `ITEM_DETAIL_REQUEST_FAILED`
export const ITEM_DETAIL_LOADED = `ITEM_DETAIL_LOADED`

Action Creators

// reducers/app/action-creators.js

import * as ACTIONS from './actions';

export const actionFactory = (type) => (payload) => ({ type, payload });

export const pageLoaded = actionFactory(ACTIONS.PAGE_LOADED);

export const itemDetailLoaded = actionFactory(
  ACTIONS.ITEM_DETAIL_LOADED
);

export const itemDetailLoadingFailed = actionFactory(
  ACTIONS.ITEM_DETAIL_REQUEST_FAILED
);

export const loadItemDetail = params => dispatch => 
  Promise.resolve()
    .then(() => fetch(`/items/${params.id}/`))
    .then((res) => res.json())
    .then(data => dispatch(itemDetailLoaded(data)))
    .catch(err => dispatch(itemDetailLoadingFailed(err)))

Reducer

// reducers/app/reducer.js

import * as ACTIONS from './actions';

export const INITIAL_STATE = {
  page: null,
  items: [],
  errors: [],
  item: null
}

const reducer = (state = INITIAL_STATE, action) => {
  switch(action.type) {
    case ACTIONS.PAGE_LOADED:
	    return {
        ...state
      }
    default:
      return state;
  }
}
export default reducer;

And finally, the index.js file will bring everything about this reducer as one thing to the world.

index.js

// reducers/app/index.js

export * from "./action-creators";
export * from "./actions";
export * from "./reducer";
export { default } from "./reducer";

Benefits

Here are some of the benefits of breaking down the reducer:

Readability

As the reducer is broken down into individual concerning files, one does not have to use the in-editor Find or dreaded long scrolls to get to the code block of interest.

Cyclic Dependency

Using a reducer would not end with having just one of them. There will be many. And many reducers will need to work together.

Reducers work together by cross-referencing each other for the actions and action creators so that they can respond to actions appropriately and change the desired part of the state.

This leads to cyclic dependencies like:

(A) → (B) → (C) → (A)

Reducer A depends on B
Reducer B depends on C
Reducer C depends on A

But A was depending on B & B was depending on C ?

So C depends on C ?‍?

But with a broken-down reducer, you can avoid cyclic dependencies as you can selectively import only the Actions or ActionCreators.

Searchability

With a Single reducer broken down into multiple files, you can fine-grain your search and pinpoint your work.

For instance, my favorite code Editor is VSCode, and I can jump to any file in the project with Cmd+Shift+P

With a broken-down reducer, I can search with app act for actions of app reducer or app act cre... to jump to action creators

Code Review

Smaller files make it easier for reviewers to review the code, leading to faster approvals and Code Merge. Hence lower code to deploy time.


Conclusion

There are many ways to write reducers. Above are two of them. I saw the benefit in the Single File reducer but learned to appreciate the split file reducer.

It is unnecessary to break down all the reducers, but considering the above benefits, I would be breaking most of them down the line.

How do you write your reducers? And does the above approach makes sense to you?

Let me know through comments. or on Twitter at @heypankaj_ and/or @time2hack

If you find this article helpful, please share it with others.

Subscribe to the blog to receive new posts right in your inbox.