Last active
February 10, 2022 18:22
Some example redux helpers for typescript projects. For illustrative purposes only, but really—you don't need another effing npm package, just write this stuff yourself.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
declare global { | |
/** | |
extend this where needed | |
*/ | |
export interface Meta { | |
session?: string; | |
receipt?: UUID; | |
} | |
} | |
export interface Action<T extends string, P extends unknown = never> { | |
type: T; | |
payload: P; | |
meta?: Meta; | |
error?: Error | string; | |
// Database properties | |
id?: Id; | |
version?: number; | |
sequence?: number; | |
sourceId?: Id; | |
createdAt?: Date; | |
} | |
type PayloadArgs<P = never> = [P] extends [never] ? [] : [P]; | |
type ActionCreatorFactory = <T extends string, P = never>(type: T) => | |
ActionCreator<T, P> extends { type: T } ? ActionCreator<T, P> : never; | |
export interface ActionCreator<T extends string, P = never> { | |
(...args: PayloadArgs<P>): Action<T, P>; | |
is: (a: any) => a is Action<T, P>; | |
type: T; | |
namespace: string; | |
} | |
export const create: ActionCreatorFactory = <T extends string, P, M>(type: T) => { | |
const creator: ActionCreator<T, P, M> = (payload: P, meta: M) => ({ | |
type, | |
payload, | |
meta | |
}); | |
creator.is = (a: any): a is Action<T, P, M> => a.type === type; | |
creator.type = type; | |
creator.namespace = type.split('/')[0]; | |
return creator; | |
} | |
export const create: ActionCreatorFactory = <T extends string, P = never>(type: T) => { | |
const creator = (...args: PayloadArgs<P>): Action<T, P> => { | |
if (args.length) { | |
return { | |
type, | |
payload: args[0], | |
}; | |
} | |
return { | |
type, | |
payload: undefined as never, | |
}; | |
}; | |
creator.is = (a: any): a is Action<T, P> => a.type === type; | |
creator.type = type; | |
creator.namespace = type.split('/')[0]; | |
return creator; | |
} | |
/** | |
Usage: | |
const create = Actions.c<EditorAction>(); | |
*/ | |
export const c = < | |
Base extends BaseAction, | |
>() => <A extends Base, P = GetPayload<A>>( | |
type: A['type'] | |
) => | |
create<A['type'], P>(type); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import * as Actions from './actions'; | |
type Start = Actions.Action<'app/start', { id: string; }>; | |
type Started = Actions.Action<'app/started', { app: App }>; | |
type AppAction = Start | Started; | |
const create = Actions.c<AppAction>(); | |
const actions = { | |
start: create<Start>('app/start'), | |
started: create<Started>('app/started'), | |
}; | |
type AppState = { | |
app?: App; | |
loading: boolean; | |
} | |
const defaultState: AppState = { | |
loading: false; | |
} | |
function reducer(state: AppState = defaultState, action: AppAction) { | |
switch (action.type) { | |
case actions.start.type: | |
return { | |
...state, | |
loading: true, | |
}; | |
case actions.started.type: | |
return { | |
...state, | |
loading: false, | |
app: action.payload.app, | |
}; | |
default: | |
return state; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment