Skip to content

Instantly share code, notes, and snippets.

@realiarthur
Last active May 4, 2023 09:04
Show Gist options
  • Save realiarthur/a16d993ed352c7c8d2fc95deb07c0b60 to your computer and use it in GitHub Desktop.
Save realiarthur/a16d993ed352c7c8d2fc95deb07c0b60 to your computer and use it in GitHub Desktop.
import { merge as dotPropMerge, set as setPropMerge } from 'dot-prop-immutable'
import { CaseReducer, PayloadAction } from '@reduxjs/toolkit'
type PropsNames<T> = Exclude<keyof T, symbol>
type DotPrefix<T extends string> = T extends '' ? '' : `.${T}`
type RecursivePropsNames<T> = {
[K in PropsNames<T>]: `${K}${DotPrefix<DotNestedKeys<T[K]>>}` | `${K}`
}[PropsNames<T>]
type DotNestedKeys<T> = (T extends object ? RecursivePropsNames<T> : '') extends infer D
? Extract<D, string>
: never
export const merge = <S>(object: S, value: any, path?: DotNestedKeys<S>) =>
path
? dotPropMerge<S, S>(object, path, value)
: ({
...object,
...value,
} as S)
export const set = <S>(object: S, value: any, path?: DotNestedKeys<S>) =>
path ? setPropMerge<S, S>(object, path, value) : (value as S)
// forward payload reducer
export function forwardSetReducer<S, P>(path?: DotNestedKeys<S>): CaseReducer<S, PayloadAction<P>> {
return (state, { payload }) => set(state, payload, path)
}
export function forwardMergeReducer<S, P>(
path?: DotNestedKeys<S>,
): CaseReducer<S, PayloadAction<P>> {
return (state, { payload }) => merge(state, payload, path)
}
// default init
const fetchInitMeta: FetchMeta = {
loading: false,
loaded: false,
error: false,
errorCode: undefined,
}
export function getMetaInitialState<S>(initialState: S, loading?: boolean): WithFetchMeta<S>
export function getMetaInitialState(): FetchMeta
export function getMetaInitialState(
initialState?: any,
loading = false,
): WithFetchMeta | FetchMeta {
return initialState !== undefined
? {
...fetchInitMeta,
data: initialState,
loading,
}
: { ...fetchInitMeta, loading }
}
// default start
export const fetchStartMeta: Partial<FetchMeta> = {
loading: true,
error: false,
}
export const startReducer: <S, P = void>(
path?: DotNestedKeys<S>,
) => CaseReducer<S, PayloadAction<P>> = path => state => merge(state, { ...fetchStartMeta }, path)
// default success
export const fetchSuccessMeta: FetchMeta = {
loading: false,
loaded: true,
error: false,
errorCode: undefined,
}
export function successReducer<S, P = void>(
path?: DotNestedKeys<S>,
): CaseReducer<S, PayloadAction<P>> {
return (state, { payload }) =>
payload !== undefined
? set(
state,
{
...fetchSuccessMeta,
data: payload,
},
path,
)
: set(
state,
{
...fetchSuccessMeta,
},
path,
)
}
// default error
export const fetchErrorMeta: Partial<FetchMeta> = {
loading: false,
error: true,
}
export const errorReducer: <S>(
path?: DotNestedKeys<S>,
) => CaseReducer<S, PayloadAction<ErrorPayload>> =
path =>
(state, { payload }) =>
merge(state, { ...fetchErrorMeta, ...payload }, path)
type ErrorPayload = {
errorCode?: import('core/errors').ERROR_CODE
}
type FetchMeta = ErrorPayload & {
loading: boolean
loaded: boolean
error: boolean
}
type WithFetchMeta<TStateSlice = void> = FetchMeta & {
data: TStateSlice
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment