Reusing Reducer Logic
As an application grows, common patterns in reducer logic will start to emerge. You may find several parts of your reducer logic doing the same kinds of work for different types of data, and want to reduce duplication by reusing the same common logic for each data type. Or, you may want to have multiple "instances" of a certain type of data being handled in the store. However, the global structure of a Redux store comes with some trade-offs: it makes it easy to track the overall state of an application, but can also make it harder to "target" actions that need to update a specific piece of state, particularly if you are using combineReducers
.
As an example, let's say that we want to track multiple counters in our application, named A, B, and C. We define our initial counter
reducer, and we use combineReducers
to set up our state:
Unfortunately, this setup has a problem. Because combineReducers
will call each slice reducer with the same action, dispatching {type : 'INCREMENT'}
will actually cause all three counter values to be incremented, not just one of them. We need some way to wrap the counter
logic so that we can ensure that only the counter we care about is updated.
Customizing Behavior with Higher-Order Reducers
As defined in Splitting Reducer Logic, a higher-order reducer is a function that takes a reducer function as an argument, and/or returns a new reducer function as a result. It can also be viewed as a "reducer factory". combineReducers
is one example of a higher-order reducer. We can use this pattern to create specialized versions of our own reducer functions, with each version only responding to specific actions.
The two most common ways to specialize a reducer are to generate new action constants with a given prefix or suffix, or to attach additional info inside the action object. Here's what those might look like:
We should now be able to use either of these to generate our specialized counter reducers, and then dispatch actions that will affect the portion of the state that we care about:
We could also vary the approach somewhat, and create a more generic higher-order reducer that accepts both a given reducer function and a name or identifier:
You could even go as far as to make a generic filtering higher-order reducer:
These basic patterns allow you to do things like having multiple instances of a smart connected component within the UI, or reuse common logic for generic capabilities such as pagination or sorting.
In addition to generating reducers this way, you might also want to generate action creators using the same approach, and could generate them both at the same time with helper functions. See Action/Reducer Generators and Reducers libraries for action/reducer utilities.