Options
All
  • Public
  • Public/Protected
  • All
Menu

stately-reducers

[api] [github]
npm

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

  • Atomic reducer: The simplest possible reducer. Handles a single action and performs a single state mutation.
  • Model reducer: A reducer composed from one or more related Atomic reducers using chain. The composed reducer comprises all actions and state mutations related to a given state shape, or Model.
  • Slice reducer: A reducer created by box. The reducer's state is maintained under a single property or "namespace" of a root object, forming a slice of the state tree.
  • Root reducer: A reducer composed from many Slice reducers using merge, whose shape is the intersection of the given reducer shapes. This is the final reducer that will be passed to createStore, 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 and box are typically used together in a single file to define a Model reducer and its containing Slice reducer. These are then composed using merge in the store definition. The advantage to this strategy over combineReducers 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, and merge. 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, and merge used together, see merge.spec.ts.

Index

Functions

Const box

  • box<K, S, A>(key: K, reducer: Reducer<S>): Reducer<object, A>
  • 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.

    Type parameters

    • K: string | number

    • S: __type

    • A: Action

    Parameters

    • key: K
    • reducer: Reducer<S>

    Returns Reducer<object, A>

Const chain

  • chain<S>(firstReducer: Reducer<S>, ...reducers: Reducer<S>[]): Reducer<S>
  • 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.

    Type parameters

    • S: __type

    Parameters

    • firstReducer: Reducer<S>
    • Rest ...reducers: Reducer<S>[]

    Returns Reducer<S>

Const checkShapeChanged

  • checkShapeChanged(shape1: __type, shape2: __type): boolean

Const collisionChecker

  • collisionChecker(statesMap: Map<Reducer, __type | undefined>): (Anonymous function)

Const consoleError

  • consoleError(reducerName1: string, reducerName2: string, key: string): void

Const getReducerName

  • getReducerName(reducer: Reducer, reducerIndex: number): string

merge

  • merge<S>(r1: Reducer<S>): Reducer<S>
  • merge<S1, S2>(r1: Reducer<S1>, r2: Reducer<S2>): Reducer<S1 & S2>
  • merge<S1, S2, S3>(r1: Reducer<S1>, r2: Reducer<S2>, r3: Reducer<S3>): Reducer<S1 & S2 & S3>
  • merge<S1, S2, S3, S4>(r1: Reducer<S1>, r2: Reducer<S2>, r3: Reducer<S3>, r4: Reducer<S4>): Reducer<S1 & S2 & S3 & S4>
  • merge<S1, S2, S3, S4, S5>(r1: Reducer<S1>, r2: Reducer<S2>, r3: Reducer<S3>, r4: Reducer<S4>, r5: Reducer<S5>): Reducer<S1 & S2 & S3 & S4 & S5>
  • merge<S1, S2, S3, S4, S5, S6>(r1: Reducer<S1>, r2: Reducer<S2>, r3: Reducer<S3>, r4: Reducer<S4>, r5: Reducer<S5>, r6: Reducer<S6>): Reducer<S1 & S2 & S3 & S4 & S5 & S6>
  • merge<S1, S2, S3, S4, S5, S6, S7>(r1: Reducer<S1>, r2: Reducer<S2>, r3: Reducer<S3>, r4: Reducer<S4>, r5: Reducer<S5>, r6: Reducer<S6>, r7: Reducer<S7>): Reducer<S1 & S2 & S3 & S4 & S5 & S6 & S7>
  • merge<S1, S2, S3, S4, S5, S6, S7, S8>(r1: Reducer<S1>, r2: Reducer<S2>, r3: Reducer<S3>, r4: Reducer<S4>, r5: Reducer<S5>, r6: Reducer<S6>, r7: Reducer<S7>, r8: Reducer<S8>): Reducer<S1 & S2 & S3 & S4 & S5 & S6 & S7 & S8>
  • 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.

    Type parameters

    • S

    Parameters

    • r1: Reducer<S>

    Returns Reducer<S>

  • Type parameters

    • S1

    • S2

    Parameters

    • r1: Reducer<S1>
    • r2: Reducer<S2>

    Returns Reducer<S1 & S2>

  • Type parameters

    • S1

    • S2

    • S3

    Parameters

    • r1: Reducer<S1>
    • r2: Reducer<S2>
    • r3: Reducer<S3>

    Returns Reducer<S1 & S2 & S3>

  • Type parameters

    • S1

    • S2

    • S3

    • S4

    Parameters

    • r1: Reducer<S1>
    • r2: Reducer<S2>
    • r3: Reducer<S3>
    • r4: Reducer<S4>

    Returns Reducer<S1 & S2 & S3 & S4>

  • Type parameters

    • S1

    • S2

    • S3

    • S4

    • S5

    Parameters

    • r1: Reducer<S1>
    • r2: Reducer<S2>
    • r3: Reducer<S3>
    • r4: Reducer<S4>
    • r5: Reducer<S5>

    Returns Reducer<S1 & S2 & S3 & S4 & S5>

  • Type parameters

    • S1

    • S2

    • S3

    • S4

    • S5

    • S6

    Parameters

    • r1: Reducer<S1>
    • r2: Reducer<S2>
    • r3: Reducer<S3>
    • r4: Reducer<S4>
    • r5: Reducer<S5>
    • r6: Reducer<S6>

    Returns Reducer<S1 & S2 & S3 & S4 & S5 & S6>

  • Type parameters

    • S1

    • S2

    • S3

    • S4

    • S5

    • S6

    • S7

    Parameters

    • r1: Reducer<S1>
    • r2: Reducer<S2>
    • r3: Reducer<S3>
    • r4: Reducer<S4>
    • r5: Reducer<S5>
    • r6: Reducer<S6>
    • r7: Reducer<S7>

    Returns Reducer<S1 & S2 & S3 & S4 & S5 & S6 & S7>

  • Type parameters

    • S1

    • S2

    • S3

    • S4

    • S5

    • S6

    • S7

    • S8

    Parameters

    • r1: Reducer<S1>
    • r2: Reducer<S2>
    • r3: Reducer<S3>
    • r4: Reducer<S4>
    • r5: Reducer<S5>
    • r6: Reducer<S6>
    • r7: Reducer<S7>
    • r8: Reducer<S8>

    Returns Reducer<S1 & S2 & S3 & S4 & S5 & S6 & S7 & S8>

Legend

  • Module
  • Object literal
  • Variable
  • Function
  • Function with type parameter
  • Index signature
  • Type alias
  • Enumeration
  • Enumeration member
  • Property
  • Method
  • Interface
  • Interface with type parameter
  • Constructor
  • Property
  • Method
  • Index signature
  • Class
  • Class with type parameter
  • Constructor
  • Property
  • Method
  • Accessor
  • Index signature
  • Inherited constructor
  • Inherited property
  • Inherited method
  • Inherited accessor
  • Protected property
  • Protected method
  • Protected accessor
  • Private property
  • Private method
  • Private accessor
  • Static property
  • Static method

Generated using TypeDoc