I have searched in a lot of ways but I am not able to find more information about conditional typing, I am trying to create a type called ChatItemMessage
and it has 2 indexes, type
and value
.
This is the current code:
type ChatItemMessage = {
type: ChatItemMessageType;
value: ChatMessage | ChatDate;
};
enum ChatItemMessageType {
Date = "date",
Message = "message",
}
type ChatMessage = {
id: number;
};
type ChatDate = {
date: string;
};
What I am trying to do is conditionally set the type of value
based on the value of type
:
export type ChatItemMessage = {
type: ChatItemMessageType;
value: (ChatItemMessage['type'] extends ChatItemMessageType.Message ? ChatMessage : ChatDate);
};
This is not working, I also tried (and other variations):
export type ChatItemMessage<T extends ChatItemMessageType> = {
type: T;
value: (T extends ChatItemMessageType.Message ? ChatMessage : ChatDate);
};
In my code I am then doing:
declare const data: ChatItemMessage[]
data.map(
message => message.type === ChatItemMessageType.Date ?
message.value.date : // error!
// ~~~~ <-- Property 'date' does not exist on type 'ChatMessage'.
message.value.id.toFixed() // error!
// ~~ <-- Property 'id' does not exist on type 'ChatDate'.
);
What I want is for message.value
to be ChatMessage
because I already checked if message.type === ChatItemMessageType.Message
, but the IDE is always saying it is ChatDate
.
The idea is for the IDE and any dev to know that if type = ChatItemMessageType.Message
then value = ChatMessage
, else (in this case) it will be ChatDate
, so new errors should show up if the dev has the wrong type in mind at runtime.
I am not even sure if it can be achieved or not. If you need more info, let me know!
For this to work you need ChatItemMessage
to be a discriminated union, where each member of the union corresponds to one of the possibilities for type
/value
pairings, so that type
can be used as a discriminant property. Like this:
type ChatItemMessage =
{ type: ChatItemMessageType.Date; value: ChatDate; } |
{ type: ChatItemMessageType.Message, value: ChatMessage; }
Once you do that your code will work as written:
data.map(
message => message.type === ChatItemMessageType.Date ?
message.value.date : // okay
message.value.id.toFixed() // okay
);