4

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.

Peter
  • 5,455
  • 7
  • 46
  • 68

3 Answers3

5

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

Aleksey L.
  • 35,047
  • 10
  • 74
  • 84
  • Thanks, I tried out all the options and this was the most concise way to achieve what I needed. – Peter Apr 19 '20 at 11:35
1

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.

davidmpaz
  • 1,194
  • 15
  • 18
  • Thank you, this answer also works. I liked this answer because it's explicit; and would use it if I needed to do the check in many places. But for a single usage it's a lot more code that the response I selected as my answer. – Peter Apr 19 '20 at 11:34
0

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

Damian Green
  • 6,895
  • 2
  • 31
  • 43
  • Thanks, but I couldn't get this to work - still got the TypeScript error *Property 'getBasicProfile' does not exist on type 'GoogleLoginResponseOffline'* withing the if block. – Peter Apr 19 '20 at 11:40
  • you said "The GoogleLoginResponseOffline type has only one property, code". So you would need to add an else clause above to handle the other type `GoogleLoginResponseOffline` – Damian Green Apr 19 '20 at 13:52