From 4f7627815be223d80262f45bd5b54c8d8a907488 Mon Sep 17 00:00:00 2001 From: David Philipson Date: Mon, 1 May 2017 11:28:49 -0700 Subject: [PATCH 1/2] Implement .caseWithAction() Allows handlers which receive the full action rather than just the payload. --- __tests__/test.ts | 13 +++++++++++++ src/index.ts | 19 +++++++++++++------ 2 files changed, 26 insertions(+), 6 deletions(-) diff --git a/__tests__/test.ts b/__tests__/test.ts index 457b454..1b12e13 100644 --- a/__tests__/test.ts +++ b/__tests__/test.ts @@ -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() .case(toBasicState, toBasicStateHandler); diff --git a/src/index.ts b/src/index.ts index 473ca6f..e4f1b62 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,7 +1,11 @@ -import { ActionCreator, AnyAction, isType } from "typescript-fsa"; +import { Action, ActionCreator, AnyAction, isType } from "typescript-fsa"; export interface ReducerBuilder { case

(actionCreator: ActionCreator

, handler: Handler): ReducerBuilder; + caseWithAction

( + actionCreator: ActionCreator

, + handler: Handler>, + ): ReducerBuilder; build(): (state: InS, action: AnyAction) => OutS; (state: InS, action: AnyAction): OutS; } @@ -24,7 +28,7 @@ export function upcastingReducer(): ReducerBuilder { actionCreator: ActionCreator

; - handler: Handler; + handler: Handler>; } type CaseList = Array>; @@ -33,14 +37,17 @@ function makeReducer(initialState?: InS): ReducerBuilder const cases: CaseList = []; const reducer = getReducerFunction(initialState, cases) as ReducerBuilder; - reducer.case =

( + reducer.caseWithAction =

( actionCreator: ActionCreator

, - handler: Handler - ): ReducerBuilder => { + handler: Handler>, + ) => { cases.push({ actionCreator, handler }); return reducer; }; + reducer.case =

(actionCreator: ActionCreator

, handler: Handler) => + reducer.caseWithAction(actionCreator, (state, action) => handler(state, action.payload)); + reducer.build = () => getReducerFunction(initialState, cases.slice()); return reducer; @@ -54,7 +61,7 @@ function getReducerFunction( 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; From 0ff3937cb3d8139e2a30dcc11f1f95b248950542 Mon Sep 17 00:00:00 2001 From: David Philipson Date: Mon, 1 May 2017 14:04:45 -0700 Subject: [PATCH 2/2] Update readme for .caseWithAction() --- README.md | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index d89b508..3e5d7f8 100644 --- a/README.md +++ b/README.md @@ -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("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. @@ -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.