I'm trying to use Redux toolkit createAction
to generate an action that take a generic type. Types are as follows.
export interface ObjectArray {
externalId: string
}
export interface CarFilters {
arrayType: string[];
objectType: ObjectArray[];
}
export type FilterOptionValue = string | number;
export type FilterMapValue = FilterOptionValue[] | string;
export interface ShipFilters {
[key: number]: FilterMapValue;
}
type AppliedFiltersType = {
cars: CarFilters;
ships: ShipFilters;
}
type PathValue<T extends object, K extends keyof T = keyof T> = T[K] extends unknown ? T[K] : never;
export type AssingValueType<
Category extends keyof AppliedFiltersType = keyof AppliedFiltersType,
Key extends keyof PathValue<AppliedFiltersType, Category> = keyof PathValue<AppliedFiltersType, Category>
> = {
category: Category,
filterKey: Key
value: PathValue<AppliedFiltersType[Category], Key>;
}
Reducer and actions
import { createAction, createReducer } from "@reduxjs/toolkit";
import { AssingValueType, AppState } from "./types";
export const updateAppliedFilterValue = createAction<AssingValueType>(
"UPDATE_APPLIED_FILTER_VALUE"
);
const initialState = {
appliedFilters: {
cars: {
arrayType: [],
objectType: []
},
ships: {}
}
} as AppState;
const reducerCreator = createReducer(initialState, (builder) => {
builder.addCase(updateAppliedFilterValue, (state, action) => {
state.appliedFilters[action.payload.category] = action.payload.value;
});
});
export default reducerCreator;
Intention is to make the action parameters strict type checked so it cannot be called with inconsistent types.
Eg
updateAppliedFilterValue({ category: 'car', filterKey: 1, value: 3 })
should be identified as a error from ts compiler.
Pure function like follows works
const updateFilterValues = <Category extends keyof AppliedFiltersType, Key extends keyof PathValue<AppliedFiltersType, Category>>(params: AssingValueType<Category, Key>) => {
return params;
}
But I'm not clear on how to create generate action with createAction
I have trouble creating action inferring the generic types.
It seems that the issue you are having is strictly related to the definition of AssigningValueType.
Solution:
export type AssingValueType = {
[Category in keyof AppliedFiltersType]:
{ category: Category } & {
[Key in keyof AppliedFiltersType[Category]]: {
filterKey: Key,
value: PathValue<AppliedFiltersType[Category], Key>
}
}[keyof AppliedFiltersType[Category]]
}[keyof AppliedFiltersType]
Explainer
AssingValueType
is a Discriminated Union Type with Category
a keyof AppliedFiltersType aka 'ships' | 'cars'
.
For each potential Category we also define filterKey
in a similar fashion. value
can be defined relative to Category and Key.
&
type intersection operator is used to merge the { filterKey, value } with { category }.
The last and most important step is the
{[Category in keyof AppliedFiltersType]: ...}[keyof AppliedFiltersType]
which extracts a union type for each of the keys of AppliedFiltersType.
In short...
{ships: {category: 'ships', ...}, cars: {category: 'cars', ...}}
becomes {category: 'cars' ...} | {category: 'ships'}
Regarding your initial concern, createAction
should work like a charm the way you intended to use it.