I am trying to create a reducer using typescript/ redux and I am facing this error
Property 'loginToken' does not exist on type '{ loginToken: string; } | { error: Error; } | { username: string; password: string; }'.
Property 'loginToken' does not exist on type '{ error: Error; }'
My reducer is as following:
export default (state = userState, action: UsersActions) => {
switch (action.type) {
case ActionTypes.USER_LOGIN_REQUEST:
return {
...state,
isAuthenticated: false,
isLoading: true,
};
case ActionTypes.USER_LOGIN_SUCCESS:
return {
...state,
isAuthenticated: true,
loginToken: action.payload.loginToken,
isLoading: false,
};
My action types:
export interface UserLoginRequest extends Action {
type: typeof ActionTypes.USER_LOGIN_REQUEST; // !! here we assign the string literal type of the constant
payload: {
username: string;
password: string;
};
}
export interface UserLoginSuccess extends Action {
type: typeof ActionTypes.USER_LOGIN_SUCCESS; // !! here we assign the string literal type of the constant
payload: {
loginToken: string;
};
}
export interface UserLoginFailure extends Action {
type: typeof ActionTypes.USER_LOGIN_FAILURE; // !! here we assign the string literal type of the constant
payload: {
error: Error;
};
}
export interface User {
username: string;
password: string;
loginToken: string;
loginError?: Error;
status?: number;
}
export type UsersActions =
| UserLoginSuccess
| UserLoginFailure
| UserLoginRequest;
Action types are an enum like this:
enum ActionTypes {
USER_LOGIN_REQUEST = 'request',
USER_LOGIN_SUCCESS = 'success',
USER_LOGIN_FAILURE = 'failure',
};
Maybe I am not getting something regarding Typescript so that's why? if any resources or insights can be provided that would be appreciated.
The problem is that UsersActions
can be one of three things - a UserLoginSuccess, a UserLoginFailure, or a UserLoginRequest - it's a Union Type.
In the "Success" case, you know that the login token will exist, and that the type is a UserLoginSuccess - but TypeScript doesn't have any way of knowing this, so it's telling you that you may be trying to get the loginToken
property from (e.g.) a UserLoginFailure
type.
The solution is that you've got to narrow the type when you want to work with it. Since there's no common properties on your union type that you could switch on, the only way I can think to do this would be to cast the payload to the correct type:
case ActionTypes.USER_LOGIN_SUCCESS:
return {
...state,
isAuthenticated: true,
loginToken: (action.payload as UserLoginSuccess).loginToken,
isLoading: false,
};
Sorry, I just re-read your question and noticed that there's a better solution - when you're creating your union types, you're using typeof
for the action type -
export interface UserLoginRequest extends Action {
type: typeof ActionTypes.USER_LOGIN_REQUEST;
// ...
}
So type
will be the type of whatever that property is - but I'm guessing what you actually want to do is make it into a known value?
I don't know what ActionsTypes
is defined as, but if this was an enum
-
enum ActionTypes {
USER_LOGIN_REQUEST = 'request',
USER_LOGIN_SUCCESS = 'success',
USER_LOGIN_FAILURE = 'failure',
};
Then you could change the type definitions to use the enum type instead -
interface UserLoginRequest extends Action {
type: ActionTypes.USER_LOGIN_REQUEST;
payload: {
username: string;
password: string;
};
}
Now, when you switch on the type
, TS will correctly narrow the payload for you -
switch (action.type) {
case ActionTypes.USER_LOGIN_SUCCESS:
console.log(action.payload.loginToken); // TS is happy!
break;
default:
console.log('NOT A SUCCESS ACTION');
}