Reducers
Last updated
Last updated
Redux are usually written using switch
statements, with each case handling a specific action type.
This pattern works reasonably well, but also has drawbacks:
There is some required boilerplate, specifically the wrapping switch (action.type)
block and the default: return state
case (the latter is easy to forget when writing a new reducer).
You have to remember the idiosyncrasies of JavaScript's switch
statements, such as the additional braces needed to define variables which should be local to a case (e.g., case 'multiply': { const factor = action.payload; … }
).
In TypeScript, ensuring that action
is typed correctly in every switch
branch requires a considerable amount of extra typing effort, often including .
Redux Preboiled has several helpers for constructing reducers which address these shortcomings. Here is how you might write the example reducer above with Preboiled:
This guide details Preboiled's reducer helpers and how they relate, plus how they help you reduce typing effort if you use TypeScript.
Note that the functions returned by onAction
are not "proper" reducers - they don't provide an initial state. For this reason, we prefer to call them sub-reducers as they are meant to be embedded into actual reducer functions. Later in this guide, we'll see how this can be done easily with the withInitialState
and chainReducers
helpers.
createAction
This removes a bit of noise as you don't need to write onAction(multiply.type, ...)
.
If you use TypeScript, there is another benefit: based on the action creator's type, the TypeScript compiler automatically infers the type of the action
argument passed to the state update function. This helps you prevent mistakes, like in the following example:
You'll also get better autocompletion in IDE's and editors which understand TypeScript, such as Visual Studio Code or WebStorm.
Using the withInitialState
helper, you can generate reducers which return a specific value as initial state.
There are two ways to use this helper. The first is to pass an initial state value and a reducer-like state update function. The resulting reducer will forward all calls to that function unless called with an undefined
state (i.e., during Redux store initialization), in which case it returns the given initial state value instead. One use case for this variant is to make a full reducer from a single onAction
sub-reducer:
Alternatively, you can specify only an initial state when calling withInitialState
. In this case, the reducer will simply return any non-undefined
state unchanged.
This second version of withInitialState
is useful for reducer chains, which we'll look at next.
chainReducers
turns a sequence of (sub-)reducers into a pipeline, or "chain", where the output state of one reducer becomes the input state for the next. Let's look at a silly, but illustrative example:
For every incoming action, the reducer above first forwards the call to uppercaseReducer
, the first reducer in the chain. It then passes the resulting state to the second reducer (lowercaseReducer
), whose return value finally becomes the state returned by the chained reducer itself.
Note that for the chain to be a proper reducer, at least one of its functions (usually the first one) must return an initial state if called with an undefined
state. A common pattern is to start the chain with a withInitialState
reducer, e.g.:
switch
As shown in this guide's introduction, you can chain multiple onAction
sub-reducers as a replacement for the switch
reducer pattern.
This works because each sub-reducer only reacts to one specific type of action, and leaves the state unchanged for all others; incoming actions will thus pass through the chain until they reach the matching sub-reducer, or leave the chain without a state change (the equivalent of default: return state
in the switch
pattern).
Preboiled's helper generates reducer-like functions which update the state only in response to actions of a specific type.
If you generate your action creators using (as described in the ), you can pass them directly to onAction
in place of their corresponding action types.
Redux comes with the function, which allows you to compose multiple reducers for different state slices. Preboiled complements this with , which is about composing reducers for the same state slice.
In addition to defining actions and reducers, Redux Preboiled also helps you with testing your Redux code. See the .