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 adduser
/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 userErrorReducer
from userReducer
and locationErrorReducer
fromlocationReducer
, 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.