Skip to content

Instantly share code, notes, and snippets.

@realiarthur
Last active November 11, 2021 17:55
Show Gist options
  • Save realiarthur/4aa1e26109bdd9d8b11d0b982d6aab87 to your computer and use it in GitHub Desktop.
Save realiarthur/4aa1e26109bdd9d8b11d0b982d6aab87 to your computer and use it in GitHub Desktop.
// Action Creator
type ActionCreator<P> = <A extends keyof P, TParams extends any[]>(
type: A,
prepare?: P[A] extends void ? undefined : (...args: TParams) => P[A],
) => (
...args: P[A] extends void ? [] : TParams extends undefined[] ? [payload: P[A]] : TParams
) => Action<A, P>
/**
* Function return action create helper that based on actions payloads interface.
*
* @param {string} type - Action type
* @param {(...args: TPrepareParams) => P[A]=} prepare - Prepare function for create action.
* If undefined and P[A] is not void - it forward payload as is.
*/
export const getActionCreator =
<P>(): ActionCreator<P> =>
(type, prepare) =>
(...args) => ({
type,
payload: (prepare ? prepare(...(args as any)) : args[0]) as any,
})
// Reducer Creator
type Reducers<S, P> = {
[Property in keyof P]: Reducer<S, P[Property]>
}
type ReducerCreator<P, S> = {
_reducers: Reducers<S, P>
add: <A extends keyof P>(type: A, reducer: Reducer<S, P[A]>) => void
create: () => (state: S | undefined, { type, payload }) => S
}
/**
* Function return reducer create helper that based on actions payloads interface.
*
* @param initialState - Initial State Slice
*/
export const getReducerCreator = <S, P>(
initialState: S,
): ReducerCreator<P, S> => ({
_reducers: Object.create({}),
add: function (type, reducer) {
this._reducers[type] = reducer
},
create: function () {
return (state = initialState as S, { type, payload }): S => {
return this._reducers.hasOwnProperty(type) ? this._reducers[type](state, payload) : state
}
},
})
interface SimpleAction<A> {
type: A
}
interface ActionWithPayload<A, P> extends SimpleAction<A> {
payload: P
}
type Action<A extends keyof P, P> = TPayloads[A] extends void ? SimpleAction<A> : ActionWithPayload<A, P[A]>
interface SimpleReducer<S> {
(state: S): S
}
interface ReducerWithPayload<S, P> {
(state: S, payload: P): S
}
type Reducer<S, P = void> = P extends void ? SimpleReducer<S> : ReducerWithPayload<S, P>
import { getActionCreator, getReducerCreator } from './typed-redux-helpers'
enum ACTIONS {
fetchStart = 'fetchStart',
fetchSuccess = 'fetchSuccess',
}
interface Payloads {
[ACTIONS.fetchStart]: void
[ACTIONS.fetchSuccess]: number
}
// Actions
export const createAction = getActionCreator<Payloads>()
export const fetchStart = createAction(ACTIONS.fetchStart)
export const fetchSuccess = createAction(ACTIONS.fetchSuccess, (text: string) => Number.parseInt(text))
// Reducer
const initialState = {
loading: false,
data: 0,
}
type StateSlice = typeof initialState
const reducer = getReducerCreator<StateSlice, Payloads>(initialState)
reducer.add(ACTIONS.fetchStart, state => ({ ...state, loading: true }))
reducer.add(ACTIONS.fetchSuccess, (state, payload) => ({ ...state, loading: false, data: payload }))
export default reducer.create()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment