How to Fix: TypeScript Property does not Exist on Union Type
TLDR: TypeScript’s “property does not exist” error on union types occurs when accessing properties not shared by all types. Use the in keyword to check for property existence and narrow the type safely. This approach enhances type safety without risky typecasting.
I was recently working on a client project and hit a TypeScript speed bump. The project is a messaging app that accepts many message inputs. The API sends payloads that allow multiple custom type values for different properties. I tried to account for this in my code using the union type but was met with the dreaded red underline. How do you fix the TypeScript error “property does not exist” on a union type?
More background on my custom union type
Let us get more context on this approach to better understand how to solve the problem. Users can send messages that include text, images, button clicks, list selections, addresses, contacts, and others.
Instead of making each type of payload its own field, the developers of the messaging client reused some of the properties to account for multiple message types. Whenever my webhook receives a message, I translate the payload to a data transfer object to check for correct fields and clean any extra properties. This DTO looks something like this:
type messageReqDTO = {
messageId: number,
senderId: number,
messagePayload: {
text: string,
interactive: ButtonType | ListType
}
}While the messagePayload field can take either of these types, the types are not the same. Here is the code for the two separate types:
type ButtonType = {
id: number
}
type ListType = {
text: string;
}When I try to access these properties in my code, I get the error that property X does not exist on the union type. While TypeScript errors are not always fatal to your app running, they do clutter up local development terminals. I needed to figure out how to solve this TypeScript error otherwise, I would get multiple errors on each message that hit my webhook.
Introducing the JavaScript and TypeScript keyword “in”
To solve this problem, we need to specify what property we are looking for in the union type. Here is where we use in. The keyword in is reserved in JavaScript and TypeScript and has 2 purposes:
- To determine the inclusion of a property in an object (JavaScript)
- To narrow down the type you are using for type safety (TypeScript)
Use 1 for in — Determining inclusion of property in an object
JavaScript uses the keyword in to determine the inclusion of a property in an object. Say we are modeling a car and have the following object:
const car = { make: "Honda", model: "Accord", year: 1998 };If there is flexibility in the car object and we do not know if there is a mileage value, we could run code like this:
'mileage' in car // falseUsing the in keyword this way will return a boolean value of true if the property is in the object and false if it is absent.
Use 2 for in — Specifying the accessible property in a union type
TypeScript has its own use for the in keyword. If we have assigned a union type to a property and either or both of the union type’s types have multiple fields we can narrow the property we want to use.
To make it clear, let us reconsider our message payload. It has a union type for button response and list response. We want to get the id off the button response:
const getMessagePayloadId(message: messageReqDTO): string {
const payload = message.messagePayload;
// Will have TypeScript error for property not existing
return payload.id;
};TypeScript does not like this because the list does not have an id, and TypeScript is not sure which of our types we are using. To get rid of the type error we can use in to narrow the selection of the property to the button-specific properties:
const getMessagePayloadId(message: messageReqDTO): string {
const payload = message.messagePayload;
if("id" in payload) {
return payload.id;
};
};Now TypeScript nows that we are explicitly accessing the ButtonType type. This is better practice than typecasting because it adds safety that prevents other developers from unknowingly changing the cast type to a non-compatible type.
Key Takeaways
- Union types in TypeScript cause errors when accessing properties unique to one type, like id in a ButtonType vs. ListType.
- The in keyword checks for property existence, narrowing the union type to ensure safe access to specific properties.
- Using in avoids typecasting, reducing errors and improving maintainability in complex messaging app DTOs.
- This method ensures type safety, preventing developers from accessing incompatible properties in union types.
