I'm seeing some very strange TypeScript behavior that I don't understand. Here is the scenario:
I have three different interfaces that describe an event. They look like this
interface ReceiveMessageEvent {
event: 'receive-message';
payload: {
action: 'receive-message';
businessSMSId: number;
message: {
businessSMSId: number;
from: string;
to: string;
};
};
}
interface ReadMessageEvent {
event: 'read-message';
payload: {
action: 'read-message';
businessSMSId: number;
customerNumber: string;
inboxNumber: string;
};
}
interface SetThreadsEvent {
event: 'set-threads';
threads: any[];
}
As you can see, all three interfaces have an event property that is a specific string, meaning there can only be three possible options, either 'receive-message', 'read-message', or 'set-threads'. This is important because next I have a switch statement where the case is this event property:
switch (action.payload.event) {
case 'receive-message':
// ...
case 'read-message':
// ...
case 'set-threads':
// ...
}
So, since the case is the event property, I'd expect TypeScript to know the shape of the rest of the data depending on which case we're in. Now, is DOES do this correctly (as I'd expect), except in one specific scenario, which is where my confusion is coming from. And that case is when I'm inside the callback function of the .filter method.
So for example:
switch (action.payload.event) {
case 'receive-message':
// This is correct and has no errors
const test = action.payload.payload.message.to
// This is giving me a TS error, even though I'm accessing the exact same property
// As in the line above
myArray.filter((thread) => thread.property === action.payload.payload.message.to)
^^^^^^^^ TS error here
case 'read-message':
// ...
case 'set-threads':
// ...
}
The exact error from the example above is this:
Property 'payload' does not exist on type 'ReceiveMessageEvent | ReadMessageEvent | SetThreadsEvent'.
Property 'payload' does not exist on type 'SetThreadsEvent'.
It's as if Typescript loses its type knowledge once I enter the callback function. The code does actually work exactly as expected, it's just TypeScript telling me there's an error even though there doesn't seem to be.
Lastly I'll note that I can do some casting like this, and then the errors disappear, although I'd prefer not to do this unless I absolutely have to):
switch (action.payload.event) {
case 'receive-message':
myArray.filter((thread) => thread.property === (action.payload as ReceiveMessageEvent).payload.message.to)
case 'read-message':
// ...
case 'set-threads':
// ...
}
Is there a reason for this behavior, or this potentially a bug within TypeScript?