Skip to content

Commit

Permalink
Merge pull request #9 from dphilipson/feature/case-with-action
Browse files Browse the repository at this point in the history
Feature/case with action
  • Loading branch information
dphilipson authored May 1, 2017
2 parents cc4e251 + 0ff3937 commit 1aa2740
Show file tree
Hide file tree
Showing 3 changed files with 49 additions and 7 deletions.
24 changes: 23 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,22 @@ const reducer = reducerWithInitialState(INITIAL_STATE)
Everything is typesafe. If the types of the action payload and handler don't line up, then
TypeScript will complain.

If the full action is needed rather than just the payload, `.caseWithAction()` may be used in
place of `.case()`. For example:
``` javascript
import { Action } from "typescript-fsa";

const setText = actionCreator<string>("SET_TEXT");

const reducer = reducerWithInitialState({ text, lastEditBy: "" })
.caseWithAction(incrementCount, (state, { payload, meta }) => ({
text: payload,
lastEditBy: meta.author,
}));

// Returns { text: "hello", lastEditBy: "cbrontë" }.
reducer(undefined, setText("hello", { author: "cbrontë" }));
```
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()`](#build) method below.

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

### Reducer chain methods

#### `.case(actionCreator, handler)`
#### `.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](#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`. For an example, see [Usage](#usage).

#### `.build()`

Returns a plain reducer function whose behavior matches the current state of the reducer chain.
Expand Down
13 changes: 13 additions & 0 deletions __tests__/test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,19 @@ describe("reducer builder", () => {
expect(reducer(initialState, dataToUpperCase)).toEqual({ data: "HELLO" });
});

it("should call full-action handler when using .caseWithAction()", () => {
const reducer = reducerWithInitialState(initialState)
.caseWithAction(sliceData, (state, action) => ({
...state,
data: state.data.slice(action.payload),
meta: { author: "cbrontë" },
}));
expect(reducer(undefined, sliceData(1, "meta"))).toEqual({
data: "ello",
meta: { author: "cbrontë" },
});
});

it("should call upcasting handler on matching action", () => {
const reducer = upcastingReducer<StateWithCount, State>()
.case(toBasicState, toBasicStateHandler);
Expand Down
19 changes: 13 additions & 6 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import { ActionCreator, AnyAction, isType } from "typescript-fsa";
import { Action, ActionCreator, AnyAction, isType } from "typescript-fsa";

export interface ReducerBuilder<InS extends OutS, OutS> {
case<P>(actionCreator: ActionCreator<P>, handler: Handler<InS, OutS, P>): ReducerBuilder<InS, OutS>;
caseWithAction<P>(
actionCreator: ActionCreator<P>,
handler: Handler<InS, OutS, Action<P>>,
): ReducerBuilder<InS, OutS>;
build(): (state: InS, action: AnyAction) => OutS;
(state: InS, action: AnyAction): OutS;
}
Expand All @@ -24,7 +28,7 @@ export function upcastingReducer<InS extends OutS, OutS>(): ReducerBuilder<InS,

interface Case<InS extends OutS, OutS, P> {
actionCreator: ActionCreator<P>;
handler: Handler<InS, OutS, P>;
handler: Handler<InS, OutS, Action<P>>;
}

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

reducer.case = <P>(
reducer.caseWithAction = <P>(
actionCreator: ActionCreator<P>,
handler: Handler<InS, OutS, P>
): ReducerBuilder<InS, OutS> => {
handler: Handler<InS, OutS, Action<P>>,
) => {
cases.push({ actionCreator, handler });
return reducer;
};

reducer.case = <P>(actionCreator: ActionCreator<P>, handler: Handler<InS, OutS, P>) =>
reducer.caseWithAction(actionCreator, (state, action) => handler(state, action.payload));

reducer.build = () => getReducerFunction(initialState, cases.slice());

return reducer;
Expand All @@ -54,7 +61,7 @@ function getReducerFunction<InS extends OutS, OutS>(
for (let i = 0, length = cases.length; i < length; i++) {
const { actionCreator, handler } = cases[i];
if (isType(action, actionCreator)) {
return handler(state, action.payload);
return handler(state, action);
}
}
return state;
Expand Down

0 comments on commit 1aa2740

Please sign in to comment.