Gathering detailed insights and metrics for @monsterkrampe/typescript-fsa-reducers
Gathering detailed insights and metrics for @monsterkrampe/typescript-fsa-reducers
Gathering detailed insights and metrics for @monsterkrampe/typescript-fsa-reducers
Gathering detailed insights and metrics for @monsterkrampe/typescript-fsa-reducers
Fluent syntax for defining typesafe reducers on top of typescript-fsa.
npm install @monsterkrampe/typescript-fsa-reducers
Typescript
Module System
67.6
Supply Chain
98.6
Quality
74.7
Maintenance
100
Vulnerability
100
License
TypeScript (98.37%)
Shell (1.16%)
JavaScript (0.47%)
Total Downloads
764
Last Day
1
Last Week
5
Last Month
15
Last Year
72
MIT License
73 Commits
2 Branches
1 Contributors
Updated on Dec 10, 2018
Minified
Minified + Gzipped
Latest Version
0.4.8
Package Id
@monsterkrampe/typescript-fsa-reducers@0.4.8
Unpacked Size
26.99 kB
Size
7.49 kB
File Count
8
Cumulative downloads
Total Downloads
Last Day
-50%
1
Compared to previous day
Last Week
0%
5
Compared to previous week
Last Month
66.7%
15
Compared to previous month
Last Year
-25%
72
Compared to previous year
1
Fluent syntax for defining typesafe Redux reducers on top of typescript-fsa.
This library will allow you to write typesafe reducers that look like this:
1const reducer = reducerWithInitialState(INITIAL_STATE) 2 .case(setName, setNameHandler) 3 .case(addBalance, addBalanceHandler) 4 .case(setIsFrozen, setIsFrozenHandler);
It removes the boilerplate normally associated with writing reducers, including if-else chains, the default case, and the need to pull the payload field off of the action.
.case(actionCreator, handler(state, payload) => newState)
.caseWithAction(actionCreator, handler(state, action) => newState)
.cases(actionCreators, handler(state, payload) => newState)
.casesWithAction(actionCreators, handler(state, action) => newState)
.default(handler(state, action) => newState)
.build()
This library allows you to define reducers by chaining a series of handlers for different action types and optionally providing an initial value. It builds on top of and assumes familiarity with the excellent typescript-fsa.
Suppose we have used typescript-fsa to define our state and some actions:
1import actionCreatorFactory from "typescript-fsa"; 2const actionCreator = actionCreatorFactory(); 3 4interface State { 5 name: string; 6 balance: number; 7 isFrozen: boolean; 8} 9 10const INITIAL_STATE: State = { 11 name: "Untitled", 12 balance: 0, 13 isFrozen: false, 14}; 15 16const setName = actionCreator<string>("SET_NAME"); 17const addBalance = actionCreator<number>("ADD_BALANCE"); 18const setIsFrozen = actionCreator<boolean>("SET_IS_FROZEN");
Using vanilla typescript-fsa
, we might define a reducer as follows:
1import { Action } from "redux"; 2import { isType } from "typescript-fsa"; 3 4function reducer(state = INITIAL_STATE, action: Action): State { 5 if (isType(action, setName)) { 6 return { ...state, name: action.payload }; 7 } else if (isType(action, addBalance)) { 8 return { 9 ...state, 10 balance: state.balance + action.payload, 11 }; 12 } else if (isType(action, setIsFrozen)) { 13 return { ...state, isFrozen: action.payload }; 14 } else { 15 return state; 16 } 17}
Using this library, the above is exactly equivalent to the following code:
1import { reducerWithInitialState } from "typescript-fsa-reducers"; 2 3const reducer = reducerWithInitialState(INITIAL_STATE) 4 .case(setName, (state, name) => ({ ...state, name })) 5 .case(addBalance, (state, amount) => ({ 6 ...state, 7 balance: state.balance + amount, 8 })) 9 .case(setIsFrozen, (state, isFrozen) => ({ ...state, isFrozen }));
Note that unlike the vanilla case, there is no need to pull the payload off of
the action, as it is passed directly to the handler, nor is it necessary to
specify a default case which returns state
unmodified.
Everything is typesafe. If the types of the action payload and handler don't line up, then TypeScript will complain. If you find it easier to read, you can of course pull out the handlers into separate functions, as shown in the Introduction.
If the full action is needed rather than just the payload, .caseWithAction()
may be used in place of .case()
. This may be useful if you intend to pass the
action unchanged to a different reducer, or if you need to read the meta
field
of the action. For example:
1import { Action } from "typescript-fsa"; 2 3const setText = actionCreator<string>("SET_TEXT"); 4 5const reducer = reducerWithInitialState({ 6 text: "", 7 lastEditBy: "", 8}).caseWithAction(incrementCount, (state, { payload, meta }) => ({ 9 text: payload, 10 lastEditBy: meta.author, 11})); 12 13// Returns { text: "hello", lastEditBy: "cbrontë" }. 14reducer(undefined, setText("hello", { author: "cbrontë" }));
Further, a single handler may be assigned to multiple action types at once using
.cases()
or .casesWithAction()
:
1const reducer = reducerWithInitialState(initialState).cases( 2 [setName, addBalance], 3 (state, payload) => { 4 // Payload has type SetNamePayload | AddBalancePayload. 5 // ... 6 }, 7);
The reducer builder chains are mutable. Each call to .case()
modifies the
callee to respond to the specified action type. If this is undesirable, see the
.build()
method below.
For this library to be useful, you will also need typescript-fsa to define your actions.
With Yarn:
yarn add typescript-fsa-reducers typescript-fsa
Or with NPM:
npm install --save typescript-fsa-reducers typescript-fsa
reducerWithInitialState(initialState)
Starts a reducer builder-chain which uses the provided initial state if passed
undefined
as its state. For example usage, see the Usage section
above.
reducerWithoutInitialState()
Starts a reducer builder-chain without special logic for an initial state.
undefined
will be treated like any other value for the state.
Redux seems to really want you to provide an initial state for your reducers.
Its createStore
API encourages it and combineReducers
function enforces it.
For the Redux author's reasoning behind this, see this
thread. For this reason,
reducerWithInitialState
will likely be the more common choice, but the option
to not provide an initial state is there in case you have some means of
composing reducers for which initial state is unnecessary.
Note that since the type of the state cannot be inferred from the initial state, it must be provided as a type parameter:
1const reducer = reducerWithoutInitialState<State>() 2 .case(setName, setNameHandler) 3 .case(addBalance, addBalanceHandler) 4 .case(setIsFrozen, setIsFrozenHandler);
upcastingReducer()
Starts a builder-chain which produces a "reducer" whose return type is a supertype of the input state. This is most useful for handling a state which may be in one of several "modes", each of which responds differently to actions and can transition to the other modes. Many programs will not have a use for this.
Note that the function produced is technically not a reducer because the initial and updated states are different type.
Example usage:
1type State = StoppedState | RunningState; 2 3interface StoppedState { 4 type: "STOPPED"; 5} 6 7interface StartedState { 8 type: "STARTED"; 9 count: number; 10} 11 12const INITIAL_STATE: State = { type: "STOPPED" }; 13 14const startWithCount = actionCreator<number>("START_WITH_COUNT"); 15const addToCount = actionCreator<number>("ADD_TO_COUNT"); 16const stop = actionCreator<void>("STOP"); 17 18function startWithCountHandler(state: StoppedState, count: number): State { 19 return { type: "STARTED", count }; 20} 21 22function addToCountHandler(state: StartedState, count: number): State { 23 return { ...state, count: state.count + count }; 24} 25 26function stopHandler(state: StartedState): State { 27 return { type: "STOPPED" }; 28} 29 30const stoppedReducer = upcastingReducer<StoppedState, State>() 31 .case(startWithCount, startWithCountHandler); 32 33const startedReducer = upcastingReducer<StartedState, State>() 34 .case(addToCount, addToCountHandler) 35 .case(stop, stopHandler); 36 37function reducer(state = INITIAL_STATE, action: Redux.Action): State { 38 if (state.type === "STOPPED") { 39 return stoppedReducer(state, action); 40 } else if (state.type === "STARTED") { 41 return startedReducer(state, action); 42 } else { 43 throw new Error("Unknown state"); 44 } 45}
.case(actionCreator, handler(state, payload) => newState)
Mutates the reducer such that it applies handler
when passed actions matching
the type of actionCreator
. For examples, see Usage.
.caseWithAction(actionCreator, handler(state, action) => newState)
Like .case()
, except that handler
receives the entire action as its second
argument rather than just the payload. This is useful if you want to read other
properties of the action, such as meta
or error
, or if you want to pass the
entire action unmodified to some other function. For an example, see
Usage.
.cases(actionCreators, handler(state, payload) => newState)
Like .case()
, except that multiple action creators may be provided and the
same handler is applied to all of them. That is,
1reducerWithInitialState(initialState).cases( 2 [setName, addBalance, setIsFrozen], 3 handler, 4);
is equivalent to
1reducerWithInitialState(initialState) 2 .case(setName, handler) 3 .case(addBalance, handler) 4 .case(setIsFrozen, handler);
Note that the payload passed to the handler may be of the type of any of the
listed action types' payloads. In TypeScript terms, this means it has type P1 | P2 | ...
, where P1, P2, ...
are the payload types of the listed action
creators.
The payload type is inferred automatically for up to four action types. After that, it must be supplied as a type annotation, for example:
1reducerWithInitialState(initialState).cases < 2 { documentId: number } > 3 ([ 4 selectDocument, 5 editDocument, 6 deleteDocument, 7 sendDocument, 8 archiveDocument, 9 ], 10 handler);
.casesWithAction(actionCreators, handler(state, action) => newState)
Like .cases()
, except that the handler receives the entire action as its
second argument rather than just the payload.
.default(handler(state, action) => newState)
Produces a reducer which applies handler
when no previously added .case()
,
.caseWithAction()
, etc. matched. The handler is similar to the one in
.caseWithAction()
. Note that .default()
ends the chain and internally does
the same as .build()
, because it is not intended that the chain be
mutated after calling .default()
.
This is useful if you need a "delegate" reducer should be called on any action after handling a few specific actions in the parent.
1const NESTED_STATE = { 2 someProp: "hello", 3}; 4 5const nestedReducer = reducerWithInitialState(NESTED_STATE) 6 .case(...); 7 8const INITIAL_STATE = { 9 someOtherProp: "world" 10 nested: NESTED_STATE 11}; 12 13const reducer = reducerWithInitialState(INITIAL_STATE) 14 .case(...) 15 .default((state, action) => ({ 16 ...state, 17 nested: nestedReducer(state.nested, action), 18 }));
.build()
Returns a plain reducer function whose behavior matches the current state of the
reducer chain. Further updates to the chain (through calls to .case()
) will
have no effect on this function.
There are two reasons you may want to do this:
You want to ensure that the reducer is not modified further
Calling .build()
is an example of defensive coding. It prevents someone
from causing confusing behavior by importing your reducer in an unrelated
file and adding cases to it.
You want your package to export a reducer, but not have its types depend
on typescript-fsa-reducers
If the code that defines a reducer and the code that uses it reside in
separate NPM packages, you may run into type errors since the exported
reducer has type ReducerBuilder
, which the consuming package does not
recognize unless it also depends on typescript-fsa-reducers
. This is
avoided by returning a plain function instead.
Example usage:
1const reducer = reducerWithInitialState(INITIAL_STATE) 2 .case(setName, setNameHandler) 3 .case(addBalance, addBalanceHandler) 4 .case(setIsFrozen, setIsFrozenHandler) 5 .build();
Copyright © 2017 David Philipson
No vulnerabilities found.
Reason
no binaries found in the repo
Reason
license file detected
Details
Reason
no SAST tool detected
Details
Reason
Found 0/30 approved changesets -- score normalized to 0
Reason
0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0
Reason
no effort to earn an OpenSSF best practices badge detected
Reason
security policy file not detected
Details
Reason
project is not fuzzed
Details
Reason
branch protection not enabled on development/release branches
Details
Reason
66 existing vulnerabilities detected
Details
Score
Last Scanned on 2025-05-05
The Open Source Security Foundation is a cross-industry collaboration to improve the security of open source software (OSS). The Scorecard provides security health metrics for open source projects.
Learn More