Skip to content

Commit 1aa2740

Browse files
authored
Merge pull request #9 from dphilipson/feature/case-with-action
Feature/case with action
2 parents cc4e251 + 0ff3937 commit 1aa2740

File tree

3 files changed

+49
-7
lines changed

3 files changed

+49
-7
lines changed

README.md

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,22 @@ const reducer = reducerWithInitialState(INITIAL_STATE)
7777
Everything is typesafe. If the types of the action payload and handler don't line up, then
7878
TypeScript will complain.
7979

80+
If the full action is needed rather than just the payload, `.caseWithAction()` may be used in
81+
place of `.case()`. For example:
82+
``` javascript
83+
import { Action } from "typescript-fsa";
84+
85+
const setText = actionCreator<string>("SET_TEXT");
86+
87+
const reducer = reducerWithInitialState({ text, lastEditBy: "" })
88+
.caseWithAction(incrementCount, (state, { payload, meta }) => ({
89+
text: payload,
90+
lastEditBy: meta.author,
91+
}));
92+
93+
// Returns { text: "hello", lastEditBy: "cbrontë" }.
94+
reducer(undefined, setText("hello", { author: "cbrontë" }));
95+
```
8096
The reducer builder chains are mutable. Each call to `.case()` modifies the callee to respond to the
8197
specified action type. If this is undesirable, see the [`.build()`](#build) method below.
8298

@@ -169,11 +185,17 @@ function reducer(state: State, action: Redux.Action): State {
169185

170186
### Reducer chain methods
171187

172-
#### `.case(actionCreator, handler)`
188+
#### `.case(actionCreator, handler(state, payload) => newState)`
173189

174190
Mutates the reducer such that it applies `handler` when passed actions matching the type of
175191
`actionCreator`. For examples, see [Usage](#usage).
176192

193+
#### `.caseWithAction(actionCreator, handler(state, action) => newState)`
194+
195+
Like `.case()`, except that `handler` receives the entire action as its second argument rather
196+
than just the payload. This is useful if you want to read other properties of the action, such as
197+
`meta` or `error`. For an example, see [Usage](#usage).
198+
177199
#### `.build()`
178200

179201
Returns a plain reducer function whose behavior matches the current state of the reducer chain.

__tests__/test.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,19 @@ describe("reducer builder", () => {
6666
expect(reducer(initialState, dataToUpperCase)).toEqual({ data: "HELLO" });
6767
});
6868

69+
it("should call full-action handler when using .caseWithAction()", () => {
70+
const reducer = reducerWithInitialState(initialState)
71+
.caseWithAction(sliceData, (state, action) => ({
72+
...state,
73+
data: state.data.slice(action.payload),
74+
meta: { author: "cbrontë" },
75+
}));
76+
expect(reducer(undefined, sliceData(1, "meta"))).toEqual({
77+
data: "ello",
78+
meta: { author: "cbrontë" },
79+
});
80+
});
81+
6982
it("should call upcasting handler on matching action", () => {
7083
const reducer = upcastingReducer<StateWithCount, State>()
7184
.case(toBasicState, toBasicStateHandler);

src/index.ts

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
1-
import { ActionCreator, AnyAction, isType } from "typescript-fsa";
1+
import { Action, ActionCreator, AnyAction, isType } from "typescript-fsa";
22

33
export interface ReducerBuilder<InS extends OutS, OutS> {
44
case<P>(actionCreator: ActionCreator<P>, handler: Handler<InS, OutS, P>): ReducerBuilder<InS, OutS>;
5+
caseWithAction<P>(
6+
actionCreator: ActionCreator<P>,
7+
handler: Handler<InS, OutS, Action<P>>,
8+
): ReducerBuilder<InS, OutS>;
59
build(): (state: InS, action: AnyAction) => OutS;
610
(state: InS, action: AnyAction): OutS;
711
}
@@ -24,7 +28,7 @@ export function upcastingReducer<InS extends OutS, OutS>(): ReducerBuilder<InS,
2428

2529
interface Case<InS extends OutS, OutS, P> {
2630
actionCreator: ActionCreator<P>;
27-
handler: Handler<InS, OutS, P>;
31+
handler: Handler<InS, OutS, Action<P>>;
2832
}
2933

3034
type CaseList<InS extends OutS, OutS> = Array<Case<InS, OutS, any>>;
@@ -33,14 +37,17 @@ function makeReducer<InS extends OutS, OutS>(initialState?: InS): ReducerBuilder
3337
const cases: CaseList<InS, OutS> = [];
3438
const reducer = getReducerFunction(initialState, cases) as ReducerBuilder<InS, OutS>;
3539

36-
reducer.case = <P>(
40+
reducer.caseWithAction = <P>(
3741
actionCreator: ActionCreator<P>,
38-
handler: Handler<InS, OutS, P>
39-
): ReducerBuilder<InS, OutS> => {
42+
handler: Handler<InS, OutS, Action<P>>,
43+
) => {
4044
cases.push({ actionCreator, handler });
4145
return reducer;
4246
};
4347

48+
reducer.case = <P>(actionCreator: ActionCreator<P>, handler: Handler<InS, OutS, P>) =>
49+
reducer.caseWithAction(actionCreator, (state, action) => handler(state, action.payload));
50+
4451
reducer.build = () => getReducerFunction(initialState, cases.slice());
4552

4653
return reducer;
@@ -54,7 +61,7 @@ function getReducerFunction<InS extends OutS, OutS>(
5461
for (let i = 0, length = cases.length; i < length; i++) {
5562
const { actionCreator, handler } = cases[i];
5663
if (isType(action, actionCreator)) {
57-
return handler(state, action.payload);
64+
return handler(state, action);
5865
}
5966
}
6067
return state;

0 commit comments

Comments
 (0)