use-immer icon indicating copy to clipboard operation
use-immer copied to clipboard

Enhance `userImmerReducer`'s type by providing function overloads

Open yifanwww opened this issue 3 years ago • 0 comments

In @types/react, they provide different useReducer overloads for the type support in different usages. There are totally 5 overloads.

function useReducer<R extends ReducerWithoutAction<any>, I>(
    reducer: R,
    initializerArg: I,
    initializer: (arg: I) => ReducerStateWithoutAction<R>,
): [ReducerStateWithoutAction<R>, DispatchWithoutAction];
function useReducer<R extends ReducerWithoutAction<any>>(
    reducer: R,
    initializerArg: ReducerStateWithoutAction<R>,
    initializer?: undefined,
): [ReducerStateWithoutAction<R>, DispatchWithoutAction];
function useReducer<R extends Reducer<any, any>, I>(
    reducer: R,
    initializerArg: I & ReducerState<R>,
    initializer: (arg: I & ReducerState<R>) => ReducerState<R>,
): [ReducerState<R>, Dispatch<ReducerAction<R>>];
function useReducer<R extends Reducer<any, any>, I>(
    reducer: R,
    initializerArg: I,
    initializer: (arg: I) => ReducerState<R>,
): [ReducerState<R>, Dispatch<ReducerAction<R>>];
function useReducer<R extends Reducer<any, any>>(
    reducer: R,
    initialState: ReducerState<R>,
    initializer?: undefined,
): [ReducerState<R>, Dispatch<ReducerAction<R>>];

The first two are for the reducers without actions, it seems that we will always need actions if we want to use useImmerReducer, so I think we can ignore them.

Follow the rest 3 overloads, I think we can also provide 3 similar useImmerReducer overloads, like

export type ImmerReducer<S, A> = (draftState: Draft<S>, action: A) => void | (S extends undefined ? typeof nothing : S);

export function useImmerReducer<S, A, I>(
    reducer: ImmerReducer<S, A>,
    initializerArg: S & I,
    initializer: (arg: S & I) => S,
): [S, Dispatch<A>];

export function useImmerReducer<S, A, I>(
    reducer: ImmerReducer<S, A>,
    initializerArg: I,
    initializer: (arg: I) => S,
): [S, Dispatch<A>];

export function useImmerReducer<S, A>(
    reducer: ImmerReducer<S, A>,
    initialState: S,
    initializer?: undefined,
): [S, Dispatch<A>];

export function useImmerReducer<S, A, I>(
    reducer: ImmerReducer<S, A>,
    initializerArg: S & I,
    initializer?: (arg: S & I) => S,
) {
    const cachedReducer = useMemo(() => produce(reducer), [reducer]);
    return useReducer(cachedReducer, initializerArg as any, initializer as any);
}

BTW: I think the name ImmerReducer could be better than Reducer since there is already a type Reducer from @types/react.

yifanwww avatar Sep 24 '22 10:09 yifanwww