Peter
Peter

Reputation: 6035

TypeScript parameter type is a union of multiple types: How can I determine which was supplied and work with it?

I'm working with the React Google Login library in TypeScript. It has type bindings for TypeScript, but all the examples are in JavaScript, and I am pretty new to TypeScript.

The setup code looks like this:

  <GoogleLogin
    clientId="client-id-value"
    onSuccess={successResponseGoogle}
    onFailure={failureResponseGoogle}
    cookiePolicy={'single_host_origin'}
  />

In TypeScript, the signature for the onSuccess callback is:

readonly onSuccess: (response: GoogleLoginResponse | GoogleLoginResponseOffline) => void

The GoogleLoginResponseOffline type has only one property, code, where GoogleLoginResponse has a range of properties to access the details of the authenticated user.

The problem that I am having is that TypeScript will not let me access any of the GoogleLoginResponse properties on the response parameter, saying, for example

"Property 'getBasicProfile' does not exist on type GoogleLoginResponseOffline'"

I have tried the following to cast or check the type of the parameter, but all give errors of one type or another. My function looks like this:

const responseGoogleSuccess = (response: GoogleLoginResponse|GoogleLoginResponseOffline) => {

  // Tried to check for property to identify type
  if(response.googleId){    // Property 'googleId' does not exist on type 'GoogleLoginResponseOffline'
      const profile = response.getBasicProfile();  // Property 'getBasicProfile' does not exist on type 'GoogleLoginResponseOffline'
  }

  // Tried to cast like this
  const typedResponse = <GoogleLoginResponse>response;

  // Tried to check type
  if(response instanceof GoogleLoginResponse){   // 'GoogleLoginResponse' only refers to a type, but is being used as a value here.
  }
}

It looks from the TypeScript documentation as if the if(response instanceof GoogleLoginResponse) is close, fails in this case because GoogleLoginResponse is an interface and it needs to be a class.

Please tell me how this is done! I've looked at lots of StackOverflow questions with similar titles, but none cover this.

Upvotes: 4

Views: 1670

Answers (3)

Aleksey L.
Aleksey L.

Reputation: 37986

You can use in operator to narrow the type:

For a n in x expression, where n is a string literal or string literal type and x is a union type, the “true” branch narrows to types which have an optional or required property n, and the “false” branch narrows to types which have an optional or missing property n

const responseGoogleSuccess = (response: GoogleLoginResponse | GoogleLoginResponseOffline) => {
  if('googleId' in response) {
      const profile = response.getBasicProfile(); // response is of type GoogleLoginResponse here
  }
}

Playground


You could of course define custom type guard, but using in operator is much easier in this case. But if you need this in several places this is how type guard can be defined:

type Reposense = GoogleLoginResponse | GoogleLoginResponseOffline;

const responseGoogleSuccess = (response: Reposense) => {
  if (isLoginResponse(response)) {
    const profile = response.getBasicProfile();
  }
}

const isLoginResponse = (response: Reposense): response is GoogleLoginResponse =>
  'googleId' in response;

Playground

Upvotes: 5

davidmpaz
davidmpaz

Reputation: 1387

What you need is a user defined type guard: https://www.typescriptlang.org/docs/handbook/advanced-types.html#user-defined-type-guards.

In you case you will check the type based on the content of the object coming at run time and not based on its structure which is the before mentioned interface

function isGoogleLoginResponse(response: GoogleLoginResponse|GoogleLoginResponseOffline): response is GoogleLoginResponse {
    // you can check more properties here off course
    return (response as GoogleLoginResponse).googleId !== undefined; 
}

With this in place, you can use the function in a if condition, then inside the if you can use the response object as GoogleLoginResponse typescript will understand that.

Upvotes: 1

Damian Green
Damian Green

Reputation: 7505

Have you tried using a type assertion, e.g.

const responseGoogleSuccess = (response: GoogleLoginResponse|GoogleLoginResponseOffline) => {

  if((response as GoogleLoginResponse).googleId){   
    // do something with (response as GoogleLoginResponse).googleId
  }
}

Typescript

Upvotes: 0

Related Questions