Skip to content

Instantly share code, notes, and snippets.

@AntanasGa
Created November 14, 2023 17:05
Show Gist options
  • Save AntanasGa/1820078b0bf274379890a5cbecb151b8 to your computer and use it in GitHub Desktop.
Save AntanasGa/1820078b0bf274379890a5cbecb151b8 to your computer and use it in GitHub Desktop.
react reducer map
import { useReducer } from "react";
type ReducerInvokerMap<S, T> = {
[K in keyof T]:T[K] extends (this: S) => S
? () => void
: T[K] extends (this: S, arg: infer Arg) => S
? Arg extends object
? (arg: Arg) => void
: never
: never
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
type Actor<S> = ((this: S) => S) | ((this: S, argument: any) => S)
export type ActorMap<S> = Record<string, Actor<S>>;
const hasNoParameter = <P, R>(fn: ((argument: P) => R) | (() => R)): fn is (() => R) => {
return !fn.length;
}
export default function useReducerMap<S extends object, R extends Record<string, Actor<S>>>(state: S, reducers: R): [S, ReducerInvokerMap<S, typeof reducers>] {
type Action = {
[K in keyof R]: R[K] extends (this: S, arg: infer Arg) => S
? Arg
: never
}[keyof R]
& { type: keyof R };
const reducerInstance = (instanceState: S, action: Action): S => {
const { type, ...initiator } = action;
const handler = reducers[type];
if (typeof handler !== "function") {
return instanceState;
}
return hasNoParameter(handler) ? handler.apply(state) : handler.apply(state, [initiator]);
};
const [reducerState, reducer] = useReducer(reducerInstance, state);
const namedReducers = {} as ReducerInvokerMap<S, typeof reducers>;
for (const k in reducers) {
type Item = (typeof namedReducers)[keyof R];
type ActParams = Item extends ((args: infer Arg) => S) ? Arg : never;
// assertion bad, but a nececery evil here
namedReducers[k] = ((act?: ActParams) => reducer({type: k, ...(act ?? {})} as Action)) as Item;
}
return [reducerState, namedReducers]
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment