Kousha
Kousha

Reputation: 36189

TypeScript Object Possibly undefined if check is not performed on the same line

Take a look at these two examples:

// Example 1
const realm = process.env.REALM;
const hasRealm = realm && ServiceClient.realms.includes(realm);
const subDomain = realm && ServiceClient.realms.includes(realm) ? `.${realm.toLowerCase()}` : '';

// Example 2
const realm = process.env.REALM;
const hasRealm = realm && ServiceClient.realms.includes(realm);
const subDomain = hasRealm ? `.${realm.toLowerCase()}` : '';

In the case of Example1, everything is fine. In the case of Example2, I get TS error TS2532: Object is possibly 'undefined'..

Is this a bug?

Upvotes: 0

Views: 101

Answers (1)

Nicholas Tower
Nicholas Tower

Reputation: 84902

Typescript does some flow control analysis to narrow down types, but your example 2 isn't written in a way that would allow it. In your first case, you have this:

const subDomain = realm && ServiceClient.realms.includes(realm) ? `.${realm.toLowerCase()}` : '';

When this line starts running, realm might be undefined. Typescript analyses the code, and since that condition includes a check on realm, it deduces that every possible way of getting into .${realm.toLowerCase()} will have excluded realm from being undefined, and therefore its type has been narrowed.

But then there's a line like this:

const subDomain = hasRealm ? `.${realm.toLowerCase()}` : '';

When this line of code starts running, again realm might be undefined. But this time, the only check is of a boolean which, from typescript's perspective, is unrelated to realm. Depending on how hasRealm was produced, there are ways to get into .${realm.toLowerCase()} with realm still undefined. You know that's impossible because you're looking at the previous line, but typescript doesn't walk through the history of how hasRealm came to be and make deductions about other types based on that.


With regard to how to fix this, one option is to insist to typescript that you know the types better than it does, with a type assertion:

const realm = process.env.REALM;
const hasRealm = realm && ServiceClient.realms.includes(realm);
const subDomain = hasRealm ? `.${(realm as string).toLowerCase()}` : '';

Be aware that this removes some typesafety, because you're telling typescript to ignore what it has figured out and trust you instead. So if you made a mistake, it will trust in your mistake.

Another option is to rearrange the way you're doing things so that the lines don't begin with the value being unknown. For example, if the check is done in the condition of an if block, then typescript can narrow the types inside the block:

const realm = process.env.REALM;
let hasRealm, subDomain;
if (realm && ServiceClient.realms.includes(realm)) {
  hasRealm = true;
  subDomain = `.${realm.toLowerCase()}`
} else {
  hasRealm = false;
  subDomain = '';
}

It's more verbose, but safer in terms of types.

Upvotes: 3

Related Questions