I am creating a react app and also using typescript. I want to have a globalState which will have several properties. I also need to use useReducer
. I create an interface for action
of the reducer
function. The interface takes in one parameter N
which will be name
of action. Now I want to set the type of data
according to the N
parameter passed.
I have almost created it using ternary operators.
export interface IGlobalState {
loading: boolean;
userData: IUserData;
}
interface IUserData {
loggedIn: boolean;
name: string;
}
type GlobalStateActionNames = "setLoading" | "setUserData"
export interface IGlobalStateAction<N extends GlobalStateActionNames = GlobalStateActionNames> {
data: N extends "setLoading"
? boolean
: N extends "setUserData"
? IUserData
: any;
name: N;
}
export const GlobalStateReducer = (
state: IGlobalState,
{name, data}: IGlobalStateAction
): IGlobalState => {
switch(name){
case "setLoading": return {...state, loading: data};
default: return state;
}
};
There is only one problem. The data
parameter in the reducer
will extends all the types which could be the possible value of data. So when I set loading
to data
it gives error.
Type
'boolean | IUserData'
'boolean | IUserData'
is not assignable to type'boolean'
I have two questions.
Note: I know I can solve this by using case "setLoading": return {...state, loading: data as boolean};
. But globalState will have many other props. So I don't want to that for all the values.
The issue is your IGlobalStateAction
, to use it as You need you would need to pass N
generic inside, as this is only way to have conditional working. As you cannot determine which action is passed to reducer, N
cannot be passed at this level. You can fix that by type guarding this type or changing it to standard union, its maybe less generic, but works without any conditional typyings.
// pay attention, type is now simple union
export type IGlobalStateAction =
| {
name: "setLoading",
data: boolean
}
| {
name: 'setUserData',
data: IUserData
}
export const GlobalStateReducer = (
state: IGlobalState,
action: IGlobalStateAction
): IGlobalState => {
switch(action.name){
case "setLoading": return {...state, loading: action.data}; // correctly infers bool
default: return state;
}
};
The second solution is, if you want to leave your conditional type - having type guard. Consider:
// here your original type
// type guard narrowing the type
const isActionWithName = <N extends GlobalStateActionNames>
(a: IGlobalStateAction, n: N): a is IGlobalStateAction<N> => a.name === n;
export const GlobalStateReducer = (
state: IGlobalState,
action: IGlobalStateAction
): IGlobalState => {
// using type guard
if (isActionWithName(action, 'setLoading')) {
return {...state, loading: action.data};
}
return state;
};
What is happening here - isActionWithName
function checks the name and puts correct generic into IGlobalStateAction
(this can be spotted here - a is IGlobalStateAction<N>
). At the point of check, we can determine which exactly name it is, and how it impacts the data
type.
Sorry for how it looks I was doing it from the phone, but you can check out my repo I have there what you ir looking for
https://github.com/EnetoJara/-eneto-react-init
export const DO_SOME = ”DO_SOME”;
export type DO_SOME = typeof DO_SOME;
interface AppAction<T,P> {
type: T;
payload?: P;
}
export function actionCreator(someParam; ParamType): AppAction<DO_SOME, ParamType> {
return {
type: DO_SOME,
payload: someParam
}
Also remember you can do something like this on them functions
function nameOfFunction<T, R> (param: T): Promise<R> {
return httpCal(whatever).then((res: R) => red);
T = type R = result Genetics that's how they are call
Then just to use it let's imagine
const a = nameOfFunction<UserModel, HttpCode> (user).then((res: HttpCode)=>res);
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.