Home > Mobile >  Typescript loads the properties which are same in all interface or types
Typescript loads the properties which are same in all interface or types

Time:09-17

I have a fetch method that uses generic types and I pass it for example three types such as UserTokens | AuthError | AuthUnHandleError, but when I want to load a property of the returned function, it only lets me load a property that exists in all three types or interfaces, the others that are not the same in all the types cannot be loaded.

export const authApiRequestSender = async <S , F> (router: string, body: object, header: Header, method: Method): Promise<S | F | AuthUnHandleError> => {
  try {
    const request = await fetch(process.env.api_url   router, {
      method: method,
      mode: 'cors',
      headers: { ...header, 'Content-Type': 'application/json' },
      body: JSON.stringify(body),
    })

    if (!request.ok) {
      return getAuthError<F>(request);
    } else {
      return getAuthSuccessResponse<S>(request);
    }
  } catch (error) {
    return createAuthUnhandledErrorObject(router)
  }
};

And after that, I use it like this:

export const userTokens = async (accessToken: string): Promise<UserTokens | AuthError | AuthUnHandleError> => {
  const response = await authApiRequestSender<UserTokens, AuthError>(
    '/auth/v1/user-tokens',
    {},
    {
      Authorization: `Bearer ${accessToken}`,
    },
    'POST'
  );
  return response;
};

And my types and interface

export type AuthError = {
  status: number | string;
  statusText: string;
  url: string;
  action: string;
  message: string;
  system: string;
  errors: Array<any> | [];
}

export interface LoginOutPut {
  status: string | number;
  action: 'login' | 'register' | 'edit_profile';
  auth: {
    access_expires_in: number;
    access_token: Token;
    access_token_type: 'access';
    refresh_expires_in: number;
    refresh_token: Token;
    refresh_token_type: 'refresh';
  };
  message: string;
  system: 'user';
  user_info: {
    email: string;
    full_name: string;
    id: string;
    status: string;
    username: string;
  };
  errors?: any;
}

type AuthNormalOutPut = Omit<LoginOutPut, 'auth'>;

export interface UserTokens extends AuthNormalOutPut {
  user_tokens_info: Array<{
    access_expires_in: number;
    create_time: number;
    last_used: number;
    os: string;
    type: 'refresh' | 'access';
  }>;
} 

For example, I can not load user_tokens_info property because does not exist in all 3 types, there is one type has it.

Thank you in advance.

CodePudding user response:

The | is a union type, and works as intended. As Typescript states, we only know for certain that the overlapping types exist on each and therefore, those are the only ones we can accept without any other typecheck. The solution for this pretty straight-forward though.

If each type has at least one field that is the same, but a different value attached to it, we can use that to allow Typescript to infer the correct type with a quick runtime check. For example

interface AuthError{
  type: "authError"
  error: "things are bad"
}
interface LoginOutput{
  type: "loginOutput"
  login: "loginSuccess"
}
const func = (e: AuthError | LoginOutput) =>{
  switch(e.type){
    case "authError":
      //Run auth error code here, as Typescript will know it's an auth error
      break;
    case "loginOutput":
      //Run loginoutput code here
     break;
    default:
     break;
  }
}

The other solution is to use user-defined type clauses. This is a little more advanced but gives you more control over how to proceed and assign types. An example would be like this:

// note: this function must return a boolean
function isAuthError(e:any):e is AuthError{
   try{
     return e.type === "authError"
    }catch(e){
     return false
   }
}
const func = (e) => {
   if(isAuthError(e)){
     //Run code if e is of type auth error
   }
}
  • Related