2

The following abstracted TS scenario:

interface EmotionsOkay {
  emotion: string;
  okay: "yap";
}
interface EmotionsNotOkay {
  emotion: string;
}
type UndetereminedEmotion = EmotionsOkay | EmotionsNotOkay;
const areYouOkay = (test: UndetereminedEmotion) => {
  console.log(test.okay ? "happy :D" : "sad D:");
};

throws a TypeScript error when console logging test.okay, because it apparently does not exist.

Property `okay` does not exist on type `UndetereminedEmotion`.

Even though it could very well exist, if the test passed to the method was of type EmotionsOkay.

Why does this happen?

AnonymousAngelo
  • 996
  • 3
  • 15
  • 37
josias
  • 1,326
  • 1
  • 12
  • 39

2 Answers2

2

At this point, TS does not know what type it is, it could be EmotionsOkay and everything will be fine, or it could be EmotionsNotOkay and the property okay would not exists. That's why you are getting this error. You need to validate which type it is.

You can use the in keyword to validate that,

interface EmotionsOkay {
  emotion: string;
  okay: "yap";
}
interface EmotionsNotOkay {
  emotion: string;
}
type UndetereminedEmotion = EmotionsOkay | EmotionsNotOkay;
const areYouOkay = (test: UndetereminedEmotion) => {
  console.log('okay' in test && test.okay ? "happy :D" : "sad D:");
};

You can't be Happy if you don't have the property okay in the first place.

If you were wondering why you can't use instanceof in this case, check out this question

Here is a playground

Nicolas
  • 8,077
  • 4
  • 21
  • 51
  • Hmm, I kinda understand it now and was able to fix my use-case w/ this. But why does a conditional check / if statement around that not fix the problem? Why is it necessary to use a TS syntax instead of a usual JS if statement around it? – josias Feb 05 '20 at 15:48
  • I'm not sure i understand where you placed your `if` but if it's in the `areYouOkay` callback,it should be fine, as long as you validate that your `test` variable implements `EmotionsOkay` before checking it's `okay` property, you should be fine. – Nicolas Feb 05 '20 at 15:52
  • [see here](http://www.typescriptlang.org/play/#code/JYOwLgpgTgZghgYwgAgKIFsD2ZiZAZwHkBrOAT2QG8AoZZCLHPALmXzClAHMBuW5TKTKsARGTgAHEXwC+1UJFiIUGbLgIA5bCXJV+DNSzYdus6mDISUAVRAATCIoagId1UxDIAvGkbqiQsgAPr6GmtpCfAh47MhwUBAAmpgArjoUPgAUkOystg5O6C5ufngAlN4AfHp0wDDImQDkguSNyKDIOWAVNHR0APT9nZYQ+AicEmDIxCCYAO4EnQAWcFMtFMD47Z5gSyhd-HTRBJgANhAAdKeYXNmjYBfryAD8yCIrEhIUzAAiIsiifBwOzIH7MERlPh0OQDIZ7BIAGnaUyKXCWUwARigIMBdtBQh4ArpMFACf4tGB0vxBmxMMg5iguI44p5oFASRd+Md8GdLtdbl1HkIytQZDwgA) – Nicolas Feb 05 '20 at 15:54
  • Sorry bad explanation, but I meant when you wrap it w/ an `if (test.okay)` or when using optional chaining. Shouldn't typescript know just as much as when using the `in` syntax? – josias Feb 05 '20 at 16:11
  • That's a good question, i don't think i have an answer for that. Sorry. @josias – Nicolas Feb 05 '20 at 16:18
0

You can use Typescript type assertion (like casting in other languages).

interface EmotionsOkay {
  emotion: string;
  okay: "yap";
}
interface EmotionsNotOkay {
  emotion: string;
}
type UndetereminedEmotion = EmotionsOkay | EmotionsNotOkay;
const areYouOkay = (test: UndetereminedEmotion) => {
  console.log((test as EmotionsOkay).okay ? "happy :D" : "sad D:");
  // or
  console.log((<EmotionsOkay>test).okay ? "happy :D" : "sad D:");
};
Daniel
  • 8,655
  • 5
  • 60
  • 87