Skip to content

Commit

Permalink
fix: use symbol for the others handler
Browse files Browse the repository at this point in the history
* fix: doc comment

* fix: use a symbol as a key for the "others" handler

* fix: more compact default handler usage (requested)

* fix: give another life to HM inference

* fix: add MergedHandlerMap type

Co-authored-by: Mohammad Hasani <thebrodmann@protonmail.com>
  • Loading branch information
Jazzmanpw and Mohammad Hasani committed Oct 17, 2020
1 parent 9c93e38 commit f5b1cee
Show file tree
Hide file tree
Showing 5 changed files with 72 additions and 45 deletions.
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`createHandlerMap handle([increment, increase], (state: number) => state + 1) (type) should match snapshot 1`] = `"HandlerMap<number, { type: \\"[Counter] increment\\"; } | { type: \\"[Counter] increase\\"; }, number>"`;
exports[`createHandlerMap handle([increment, increase], (state: number) => state + 1) (type) should match snapshot 1`] = `"CustomHandlerMap<number, { type: \\"[Counter] increment\\"; } | { type: \\"[Counter] increase\\"; }, number>"`;

exports[`createHandlerMap handle(increment, (state: number) => state + 1) (type) should match snapshot 1`] = `"HandlerMap<number, { type: \\"[Counter] increment\\"; }, number>"`;
exports[`createHandlerMap handle(increment, (state: number) => state + 1) (type) should match snapshot 1`] = `"CustomHandlerMap<number, { type: \\"[Counter] increment\\"; }, number>"`;

exports[`createHandlerMap handle.others((state: number) => state + 1) (type) should match snapshot 1`] = `"{ default: Handler<number, any, number>; }"`;
exports[`createHandlerMap handle.others((state: number) => state + 1) (type) should match snapshot 1`] = `"OthersHandlerMap<number, any, number>"`;
4 changes: 2 additions & 2 deletions src/__tests__/create-handler-map.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { createActionCreator } from '../create-action-creator'
import { createHandlerMap as handle } from '../create-handler-map'
import { createHandlerMap as handle, othersHandlerKey } from '../create-handler-map'

describe('createHandlerMap', () => {
it('should belong one action to one handler', () => {
Expand All @@ -19,6 +19,6 @@ describe('createHandlerMap', () => {

it('should put the "others" handler by "default" key', () => {
const reducer = (state: number) => state + 1
expect(handle.others(reducer)).toEqual({ default: reducer });
expect(handle.others(reducer)).toEqual({ [othersHandlerKey]: reducer });
})
})
91 changes: 58 additions & 33 deletions src/create-handler-map.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,42 @@ import { AnyAction } from './create-action'
import { getType } from './get-type'
import { Handler } from './types'

export type HandlerMap<
export const othersHandlerKey = Symbol('others');

type CustomHandlerMap<
TPrevState,
TAction extends AnyAction,
TNextState extends TPrevState = TPrevState
> = { [type in TAction['type']]: Handler<TPrevState, TAction, TNextState> }

type OthersHandlerMap<
TPrevState,
TAction extends AnyAction,
TNextState extends TPrevState = TPrevState
> = { [othersHandlerKey]: Handler<TPrevState, TAction, TNextState> }

export type HandlerMap<
TPrevState,
TAction extends AnyAction,
TNextState extends TPrevState = TPrevState
> =
| CustomHandlerMap<TPrevState, TAction, TNextState>
| OthersHandlerMap<TPrevState, TAction, TNextState>

export type MergedHandlerMap<
TPrevState,
TAction extends AnyAction,
TNextState extends TPrevState = TPrevState
> = CustomHandlerMap<TPrevState, TAction, TNextState> &
OthersHandlerMap<TPrevState, TAction, TNextState>

export type InferActionFromHandlerMap<
THandlerMap extends HandlerMap<any, any>
> = THandlerMap extends HandlerMap<any, infer T> ? T : never
> = THandlerMap extends CustomHandlerMap<any, infer T> ? T : never

export type InferNextStateFromHandlerMap<
THandlerMap extends HandlerMap<any, any>
> = THandlerMap extends HandlerMap<any, any, infer T> ? T : never
> = THandlerMap extends CustomHandlerMap<any, any, infer T> ? T : never

type InferActionFromCreator<TActionCreator> = TActionCreator extends (...args: any[]) => infer T ? T : never

Expand All @@ -25,7 +48,7 @@ type CreateOthersHandler<TPrevState> = <
TAction extends AnyAction = InferActionFromCreator<TActionCreator>
>(
handler: Handler<TPrevState, TAction, TNextState>
) => { default: Handler<TPrevState, TAction, TNextState> }
) => OthersHandlerMap<TPrevState, TAction, TNextState>

type CreateCustomHandlerMap<TPrevState> = <
TActionCreator extends ActionCreator<any>,
Expand All @@ -34,7 +57,7 @@ type CreateCustomHandlerMap<TPrevState> = <
>(
actionCreators: TActionCreator | TActionCreator[],
handler: Handler<TPrevState, TAction, TNextState>
) => HandlerMap<TPrevState, TAction, TNextState>
) => CustomHandlerMap<TPrevState, TAction, TNextState>

export type CreateHandlerMap<TPrevState> = CreateCustomHandlerMap<TPrevState> & {
others: CreateOthersHandler<TPrevState>
Expand All @@ -48,32 +71,34 @@ export type CreateHandlerMap<TPrevState> = CreateCustomHandlerMap<TPrevState> &
* @example
* createHandlerMap([increment, increase], (state: number) => state + 1)
* @example
* createHandlerMap.default((state: number) => state + 1)
* createHandlerMap.others((state: number) => state + 1)
*/
export const createHandlerMap = Object.assign(
<
TActionCreator extends ActionCreator<any>,
TPrevState,
TNextState extends TPrevState,
TAction extends AnyAction = InferActionFromCreator<TActionCreator>
>(
actionCreators: TActionCreator | TActionCreator[],
handler: Handler<TPrevState, TAction, TNextState>
) => {
return (Array.isArray(actionCreators) ? actionCreators : [actionCreators])
.map(getType)
.reduce<HandlerMap<TPrevState, TAction, TNextState>>((acc, type) => {
acc[type] = handler
return acc
}, {} as any)
},
{
others: <TActionCreator extends ActionCreator<any>,
TPrevState,
TNextState extends TPrevState,
TAction extends AnyAction = InferActionFromCreator<TActionCreator>
>(
handler: Handler<TPrevState, TAction, TNextState>
) => ({ default: handler })
}
)
export function createHandlerMap<
TActionCreator extends ActionCreator<any>,
TPrevState,
TNextState extends TPrevState,
TAction extends AnyAction = InferActionFromCreator<TActionCreator>
>(
actionCreators: TActionCreator | TActionCreator[],
handler: Handler<TPrevState, TAction, TNextState>
): CustomHandlerMap<TPrevState, TAction, TNextState> {
return (Array.isArray(actionCreators) ? actionCreators : [actionCreators])
.map(getType)
.reduce<CustomHandlerMap<TPrevState, TAction, TNextState>>((acc, type) => {
acc[type] = handler
return acc
}, {} as any)
}

createHandlerMap.others = createOthersHandlerMap

function createOthersHandlerMap<
TActionCreator extends ActionCreator<any>,
TPrevState,
TNextState extends TPrevState,
TAction extends AnyAction = InferActionFromCreator<TActionCreator>
>(
handler: Handler<TPrevState, TAction, TNextState>
): OthersHandlerMap<TPrevState, TAction, TNextState> {
return { [othersHandlerKey]: handler }
}
13 changes: 8 additions & 5 deletions src/create-reducer.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import {
createHandlerMap,
othersHandlerKey,
CreateHandlerMap,
HandlerMap,
InferActionFromHandlerMap,
InferNextStateFromHandlerMap,
MergedHandlerMap,
} from './create-handler-map'
import { merge } from './utils'
import { AnyAction } from './create-action'
Expand All @@ -24,16 +26,17 @@ export function createReducer<
defaultState: TPrevState,
handlerMapsCreator: (handle: CreateHandlerMap<TPrevState>) => THandlerMap[]
) {
const handlerMap = merge(...handlerMapsCreator(createHandlerMap))
const handlerMap: MergedHandlerMap<TPrevState, any, any> = merge(
...handlerMapsCreator(createHandlerMap)
)

return (
state = defaultState,
action: InferActionFromHandlerMap<THandlerMap> | AnyAction
): InferNextStateFromHandlerMap<THandlerMap> => {
const handler = handlerMap[(<any>action).type]
const handler =
handlerMap[(<AnyAction>action).type] || handlerMap[othersHandlerKey]

return handler ? handler(<any>state, action) :
handlerMap.default ? handlerMap.default(<any>state, action) :
state
return handler ? handler(<any>state, action) : state
}
}
3 changes: 1 addition & 2 deletions src/utils/data.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
export const merge = <T extends {}>(...objs: T[]): T =>
Object.assign({}, ...objs)
export const merge = <T>(...objs: T[]): any => Object.assign({}, ...objs)

export function castArray<TValue>(
value: TValue | ReadonlyArray<TValue>
Expand Down

0 comments on commit f5b1cee

Please sign in to comment.