import { useMemo, useReducer, type Dispatch } from "react"; import { type Saga } from "redux-saga"; import { put, type CallEffect, type SelectEffect } from "redux-saga/effects"; import useSagaReducer from "use-saga-reducer"; /** * react-state-factory use actions in * { type: string, payload: any } format */ export type ActionType = T extends { type: infer U, payload: infer P } ? { type: U, payload: P } : never; /** * Labes use for automatic create labels by user created ActionsMaper type * which looks like * ```ts * export type ActionsMap = * | { type: "FOO_ACTION", payload: string } * | { type: "ADD_ITEM", payload: Item } * | { type: "REMOVE_ITEM", payload: ItemId } * * export const label:Labels = {} * ``` */ export type Labels> = { [K in AM['type']]: K } export type TypedActionMap< AM extends ActionType, EM extends Labels > = { [T in keyof EM]: (payload: Extract, { type: T }>["payload"]) => void; }; export function typedActionMapFactory< AM extends ActionType >( keys: Labels, dispatch: Dispatch ): TypedActionMap> { return Object.keys(keys).reduce((acc, type) => ({ ...acc, [type]: (payload: any) => { dispatch({ type, payload } as AM); } }), {} as TypedActionMap>); } export function typedPutActionMapFactory< AM extends ActionType >( labels: Labels ): TypedActionMap> { return Object.keys(labels).reduce((acc, type) => ({ ...acc, [type]: (payload: any) => put({ type, payload }) }), {} as TypedActionMap>); } export type UseFactoryReturn = [state: ST, put: Put]; export const useStateFactory = < AM extends ActionType, ST, PT extends Labels, >( reducer: (st: ST, action: AM) => ST, initialState: ST, labels: PT, ) => { type FactoryResult = [state: ST, dispatch: Dispatch]; const [state, dispatch]: FactoryResult = useReducer(reducer, initialState); const put = useMemo( () => typedActionMapFactory(labels, dispatch), [dispatch, labels] ); return [state, put] as UseFactoryReturn>; }; export type GeneratorSaga = () => Generator | SelectEffect, void, ST>; export const useSagaFactory = < AM extends ActionType, ST, PT extends Labels, >( reducer: (st: ST, action: AM) => ST, initialState: ST, labels: PT, saga: GeneratorSaga ) => { type SagaResult = [state: ST, dispatch: Dispatch]; const [state, dispatch]: SagaResult = useSagaReducer(saga as any as Saga, reducer, initialState); const put = useMemo( () => typedActionMapFactory(labels, dispatch), [dispatch, labels] ); return [state, put] as UseFactoryReturn>; };