Creates a "namespace" for a reducer.
Given Reducer<S>
and key K
, returns a new reducer whose shape is { [K]: S }
.
type box = <S, K>(reducer: Reducer<S>, key: K) => Reducer<{ [K]: S }>
For a working example, see box.spec.ts
.
The chain
composer is nearly identical to the commonly-used reduceReducers
composer.
It behaves identically to the pipe
function found in most functional programming libraries;
however, it constrains the input reducers by forcing them all to accept and return the same state shape.
The intended use case is to compose reducers which together manage a single slice of a state tree.
An example use case is a situation where multiple actions affect a state shape in different ways.
This can be preferential to avoid writing reducers using e.g. a switch
, or complex, nested ternary matchers.
If you want to compose multiple reducers that each handle separate slices of a state tree, you should use the merge composer instead.
For a working example, see chain.spec.ts
.
The merge
composer is similar to chain, but each given reducer
is called in isolation - it is not given the returned states of the other reducers, and the final
state tree is collision-checked to ensure that shapes returned by one reducer do not overlap
with shapes returned by any other.
The intended use case is to compose reducers which each assume complete ownership of their part of a state tree, together creating a root state shape.
For example:
type A = { a: {} }
type B = { b: {} }
type X = { x: {} }
type Y = { y: {} }
const reducer1: Reducer<A> = ...
const reducer2: Reducer<B> = ...
const reducer3: Reducer<X & Y> = ...
const composed = merge(reducer1, reducer2, reducer3)
// typeof composed: Reducer<{ a: {}, b: {}, x: {}, y: {} }>
In this way, it is similar to combineReducers
, but rather than expecting all of the
"owned slices" of every integrated reducer to be defined by the store owner, each
reducer instead determines the slice(s) that it owns. This allows external libraries
to define reducers that can be integrated into your store without requiring you to
be aware of their desired "slice name(s)".
When an action is dispatched, each of the reducers composed using this function is called with the last state object that it returned. In practice, this means that every reducer is treated as if it is the only reducer in the store - it will never receive state values returned by any other reducer.
The final state tree is composed by merging the returned states of each sub-reducer. The
merge is done in-order, and destructively. For that reason, reducers passed to merge
should
not return any properties that are also returned by any other. This function will log an
error to the console if any overwrite is detected.
If you find that you must compose reducers with overlapping state shapes and you cannot change them, consider isolating one of them using $box. This will mean that components accessing the state of the boxed reducer will need to unbox its state before using it.
If you are defining a set of reducers that is intended to manage a shared subset of the state tree, use the chain composer instead.
For a working example of chain, box, and merge used together, see merge.spec.ts
.
Generated using TypeDoc
stately-reducers
[api] [github]

This module contains functional composers for Redux reducers. Used together, they allow a complex state model to be expressed by composing simple, atomic reducers.
Definitions
chain
. The composed reducer comprises all actions and state mutations related to a given state shape, or Model.box
. The reducer's state is maintained under a single property or "namespace" of a root object, forming a slice of the state tree.merge
, whose shape is the intersection of the given reducer shapes. This is the final reducer that will be passed tocreateStore
, and thus forms the root of the state tree.Atomic reducers are succinct, readable, testable, and do not require branching logic such as
switch
statements or nested ternary expressions. By defining all of your state management using atomic reducers, then composing them into more complex state trees, many logic bugs and organizational problems can be prevented. Atomic reducers can also be reused by more than one model, keeping code DRY.chain
andbox
are typically used together in a single file to define a Model reducer and its containing Slice reducer. These are then composed usingmerge
in the store definition. The advantage to this strategy overcombineReducers
is that the shape of a reducer is defined where the reducer is defined, rather than where the store is defined. Instead of having to go to the store definition to understand where the data from a reducer will live in the state tree, it is evident in the definition of the reducer itself.Usage
The composers in this module are
chain
,box
, andmerge
. They are typically used in that order, following a composition strategy that progresses from the specific (atomic) to the general (composed).TypeScript itself does not have sufficient syntax sugar to express the types succinctly. However, using some pseudo-type syntax, the composers look something like:
// many atomic reducers with the same state shape become a model reducer type chain = <S>(...reducers: Reducer<S>[]) => Reducer<S> // Reducer<S> becomes Reducer<{ key: S }>, forming a slice reducer type box = <K, S>(key: K, reducer: Reducer<S>) => Reducer<{ [K]: S }> // many independent reducers with different shapes become a root reducer type merge = <...S>(...reducers: Reducer<...S>[]) => Reducer<Intersection<...S>>
The following pseudo-example takes three sets of atomic reducers A, B, and C, composing them into models, slices, and finally a root reducer in a single expression:
const aReducers: Reducer<A>[] const bReducers: Reducer<B>[] const cReducers: Reducer<C>[] const rootReducer = merge( box('a', chain(...aReducers)), box('b', chain(...bReducers)), box('c', chain(...cReducers)), ) // typeof rootReducer: Reducer<{ a: A, b: B, c: C }>
For a working example of
chain
,box
, andmerge
used together, seemerge.spec.ts
.