Say I have the following types:
type MessageType = 'example1' | 'example2' | 'example3'
type MessageHead = {
+type: MessageType
}
type BaseBody = {
+payload?: any,
+data?: any
}
type LabelledBody = {
+labelName: string
}
type MessageBody = BaseBody | LabelledBody
type Message = MessageHead & MessageBody
And I then consume a message like so:
[{name: 'example1'}, {name: 'potato'}].find(thing => thing.name === message.labelName)
Resulting in the following flow exception:
Cannot get message.labelName because:
• all branches are incompatible:
• Either property labelName is missing in MessageHead [1].
• Or property labelName is missing in BaseBody [2].
• ... 1 more error.
With type Message = MessageHead & MessageBody
being displayed as the violated type
What I don't understand is why my Union Type doesn't allow for a message with a labelname?
Edit: Tryflow link:Tryflow link
TypeScript Union Type TypeScript allows a flexible type called any that can be assigned to a variable whose type is not specific. On the other hand, TypeScript allows you to combine specific types together as a union type. let answer: any; // any type. let typedAnswer: string | number; // union type.
TypeScript follows possible paths of execution that our programs can take to analyze the most specific possible type of a value at a given position. It looks at these special checks (called type guards) and assignments, and the process of refining types to more specific types than declared is called narrowing.
The "Property does not exist on type String" error occurs when we try to access a property that does not exist on the string type. To solve the error, use an object instead of a string, or make sure you're accessing a valid built-in method on the string.
The "Property does not exist on type Object" error occurs when we try to access a property that is not contained in the object's type. To solve the error, type the object properties explicitly or use a type with variable key names.
Your union type does allow for a Message
with a labelname. The problem is that it also allows for a Message
without a label name. Consider the union you've got on this line:
type MessageBody = BaseBody | LabelledBody
This means that MessageBody
can be either look like this:
type BaseBody = {
+payload?: any,
+data?: any
}
or
type LabelledBody = {
+labelName: string
}
Taking this a step further, we can perform the intersection between MessageBody and MessageHead and see that the shape of Message can one of these two cases:
(case 1)
{
+type: MessageType, // From MessageHead
+payload?: any, // From BaseBody
+data?: any // From BaseBody
}
(case 2)
{
+type: MessageType, // From MessageHead
+labelName: string // From LabelledBody
}
So when Flow sees you're accessing the labelName of the message
object, it believes (correctly), that the message
object might look like case 1 (above). If we're in case 1, then you can't access the labelName, since it doesn't exist, so it throws an error. The simplest way to get around this is to create two types of messages, one with a labelName
and one with the payload
and data
properties. Then you can annotate your function as receiving one of the types:
(Try)
type MessageWithLabel = MessageHead & LabelledBody
const exFunc = (message: MessageWithLabel) => {
[{name: 'example1'}, {name: 'potato'}].find(thing => thing.name === message.labelName)
}
Alternatively, you can use a disjoint union to tell flow which case you're using. This strategy involves setting a property (e.g. type
) which tells flow which type of object we're dealing with.
(Try)
type MessageWithBody = {|
+type: 'base',
+payload?: any,
+data?: any
|}
type MessageWithLabel = {|
+type: 'labelled',
+labelName: string
|}
type Message = MessageWithBody | MessageWithLabel
const exFunc = (message: Message) => {
if (message.type === 'labelled') {
const labelName = message.labelName
return [{name: 'example1'}, {name: 'potato'}].find(thing => thing.name === labelName)
// Sidenote:
// I had to extract labelName to a constant for Flow to typecheck this correctly.
// So if we used thing.name === message.labelName Flow will throw an Error.
// If you don't extract it right away, flow thinks the Message may have changed
// after pretty much any non-trivial action. Obviously it doesn't change in this
// example, but, hey, Flow isn't perfect.
// Reference: https://flow.org/en/docs/lang/refinements/#toc-refinement-invalidations
} else {
// Do something for the 'base' type of message
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With