I want to set Typescript types for Create Reducer and I want to understand How can I use Generics in this hard construction.
Here I have Actions
State
Object where Keys is name of action type and value is function with 3 parameter (payload, state, type)
I want to make Auto types based on Passed Types to createReducer function
I have next example
enum ActionTypes {
INCREMENT = 'INCREMENT',
DECREMENT = 'DECREMENT',
SET_COUNT = 'SET_COUNT',
}
type IncrementAction = IAction<ActionTypes.INCREMENT>;
type DecrementAction = IAction<ActionTypes.DECREMENT, 'hello' | 'world'>;
type SetCountAction = IAction<ActionTypes.SET_COUNT, number>;
type CounterState = {
count: number;
message?: string;
};
const state: CounterState = {
count: 0,
};
type Action = IncrementAction | DecrementAction | SetCountAction;
// I want auto type inference for the action type
const counterReducer = createReducer<CounterState, Action>(state, {
// [ActionTypes.INCREMENT]: (payload: unknown, state: CounterState, type: ActionTypes.INCREMENT)
[ActionTypes.INCREMENT]: (payload, state, type) => ({ count: state.count + 1 }),
// [ActionTypes.DECREMENT]: (payload: 'hello' | 'world', state: CounterState, type: ActionTypes.DECREMENT)
[ActionTypes.DECREMENT]: (payload, state, type) => ({ count: state.count - 1 }),
// [ActionTypes.SET_COUNT]: (payload: number, state: CounterState, type: ActionTypes.SET_COUNT)
[ActionTypes.SET_COUNT]: (payload, state, type) => ({ count: payload }),
['*']: (payload: unknown, state: CounterState, type: string) => ({}),
// here should be Error becase is not member of ActionTypes
['something']: (payload: unknown, state: CounterState, type: string) => ({}),
});
So, I have some code for it, but I cant resolve all types
export type IState = Record<string, any>;
export type IAction<T extends string = string, P = unknown> = {
type: T;
payload?: P;
};
export type Reducer<S, A extends IAction> = (
payload: A['payload'],
state: S,
type: A['type'],
) => IState;
interface IHandlers<S, A extends IAction, T extends IAction['type'] = string> {
[key: T]: Reducer<S, A>;
['*']: Reducer<S, A>;
}
export function createReducer<S extends IState, A extends IAction>(
initialState: S = {} as S,
handlers: IHandlers<S, A>,
) {
return function reducer(state = initialState, action: A) {
let newState = state;
if (handlers.hasOwnProperty(action.type)) {
const result = handlers[action.type](action.payload, state, action.type);
newState = { ...state, ...result };
}
if (handlers['*']) {
return { ...newState, ...handlers['*'](action.payload, newState, action.type) };
}
return newState;
};
}
export default createReducer;