How to manage redux state using reduceReducers.

Usually when someone creates a redux app, the root reducer gets configured within combineReducers utility.

Suppose we have a simple state, that with fieldsuser, location and also it should track failures related to fetch of user or location. Also location failures must be displayed as warnings, and user related as errors. Those errors should be displayed as stack:

We could add a field called errors to both user and location sub-states, and our state would look like:

Advantages:

  • Any domain of the application can handle error tracking separately.
  • In case of successful fetch of location or user, corresponding errors might be easily removed from state.

Disadvantages:

  • Hard to select all errors in the app, and display those in the error stack. Hypothetic selectErrors function should be aware of all modules in the app, that might track errors.
  • Modules should reason not only about logging errors, but also about removing those with cross button.

Another potential approach would be in creating separate sub-state for errors:

Advantages:

  • Easy to select all active errors.
  • You can write separate reducer, that will handle removing of any error with cross button.

Disadvantages:

  • errors reducer should handle failure actions for all modules in the app.
  • In case of successful fetch of location or user (and any mid-size app will have at least 5–10 modules), corresponding errors might be removed within logic inside errors reducer.

The main issue of both approaches occurs due to assumption, that any reducer should manage only its own part of the state. This is paradigm, brought to us by combineReducers , which will delegate all top level state values to individual reducers. Hence there’s no way to modify anything outside of area of responsibility of certain reducer.

As an alternative, full state might be passed and returned by each individual reducer:

You might notice, that in rootReducer state is explicitly piped through each reducer. This means that userReducer will manage previous store state, then result of userReducer will be passed as state parameter to the locationReducer and so on. This actually means that if reducers would be stored in an array, there could be a universal function that would organize that pipeline. This function is called reduceReducers and it can be imported from `reduce-reducers` npm package. Reducer code can now be simplified:

Advantages:

  • You can easily select all errors, and manage them in the errorsReducer . However you can still add user / location related errors in appropriate reducers.
  • You can post-process your state. For example if you never want to store more than 7 errors, you can just create a errorsCleanUpReducer and add it as last reducer in the list.

Disadvantages:

  • Every reducer is now aware of full state, it gets harder to perform update operations.

However reduceReducers is not a replacement of combineReducers , in fact you may try use them together. Let extract userErrorReducerfrom userReducer and locationErrorReducerfromlocationReducer , those will just manage errors related to their modules. Now we can reduce all errors related reducers, and combine them together with user and location reducers.

Now each module can track its own errors, however those errors are stored in one source of truth. To get more familiar with this approach play with example above on this codesandbox.

React / typescript entusiast