I have the following interfaces:
interface AppState {
readonly grid : IGridSettings;
readonly selected : ISelectedSettings;
}
interface IGridSettings {
readonly extents : number;
readonly isXY : boolean;
readonly isXZ : boolean;
readonly isYZ : boolean;
readonly spacing : number;
}
interface ISelectedSettings {
readonly bodyColor : number;
readonly colorGlow : number;
readonly lineColor : number;
readonly selection : number[] | undefined;
}
interface Action<T = any> {
type: T
}
interface SetPropertyValueAction<KAction extends keyof AppState, KActionProp extends keyof AppState[KAction]> extends Action {
type : string;
payload : {
property : [KAction, KActionProp];
value : IUserActions[KAction][KActionProp];
};
}
Eventually my redux reducer gets called when the setPropertyValue function is emitted:
const setPropertyValue = <KAction extends keyof AppState, KActionProp extends keyof AppState[KAction]>( property : [KAction, KActionProp], value : AppState[KAction][KActionProp] ) : SetPropertyValueAction<KAction, KActionProp> => ( {
type: 'SET_PROPERTY_VALUE',
payload: {
property,
value,
}
} );
function myReducer( state : AppState = INITIAL_STATE, a : Action ) : AppState {
switch( a.type ) {
case 'SET_PROPERTY_VALUE': {
const action = a as SetPropertyValueAction; // <- error here
return createNewState( state, action.payload.property, action.payload.value );
}
}
return states;
}
The problem that I'm having is I don't know how to fix the cast from Action to SetPropertyValueAction. TypeScript tells me the error is:
Generic type 'SetPropertyValueAction<KAction, KActionProp>' requires 2 type argument(s).
I tried changing the line of code that has the error to the following:
const action = a as SetPropertyValueAction<KAction extends keyof AppState, KActionProp extends keyof AppState[KAction]>;
...but this gives me two errors saying 'Cannot find name KAction and KActionProp'
How can I cast an Action object to the generic SetPropertyValueAction ??
This is a follow up question to my other question that has been answered here: How to add TypeScript type safety to my redux action?
We need to specify the type parameters for the type. Since we don't really know them, we need something that is compatible with keyof T
but represent is not an actual key of T
. unknown
will not do since it does not satisfy the constraint, the one type we can use is any
or never
(which is a subtype of any type)
interface AppState {
readonly grid: IGridSettings;
readonly selected: ISelectedSettings;
}
interface IGridSettings {
readonly extents: number;
readonly isXY: boolean;
readonly isXZ: boolean;
readonly isYZ: boolean;
readonly spacing: number;
}
interface ISelectedSettings {
readonly bodyColor: number;
readonly colorGlow: number;
readonly lineColor: number;
readonly selection: number[] | undefined;
}
interface Action<T = any> {
type: T
}
interface SetPropertyValueAction<KAction extends keyof AppState, KActionProp extends keyof AppState[KAction]> extends Action {
type: string;
payload: {
property: [KAction, KActionProp];
value: AppState[KAction][KActionProp];
};
}
const setPropertyValue = <KAction extends keyof AppState, KActionProp extends keyof AppState[KAction]>(property: [KAction, KActionProp], value: AppState[KAction][KActionProp]): SetPropertyValueAction<KAction, KActionProp> => ({
type: 'SET_PROPERTY_VALUE',
payload: {
property,
value,
}
});
declare const INITIAL_STATE: AppState;
// implementation if necessary
function createNewState<KAction extends keyof AppState, KActionProp extends keyof AppState[KAction]>(state: AppState, property: [KAction, KActionProp], value: AppState[KAction][KActionProp]): AppState {
return Object.assign({ ...state }, {
[property[0]]: Object.assign({ ...state[property[0]] }, {
[property[1]]: value,
})
});
}
function myReducer(state: AppState = INITIAL_STATE, a: Action): AppState {
switch (a.type) {
case 'SET_PROPERTY_VALUE': {
const action = a as SetPropertyValueAction<never, never>; // we don't know the actual type, never will have to do
return createNewState(state, action.payload.property, action.payload.value);
}
}
return state;
}