An introduction to Typescript Discriminated Unions
/ 2 min read
Learn a Typescript pattern for reducing unnecessary optional values and improving type safety.
Discriminated Unions
Typescript Unions provide us a powerful feature that allows discrimination between different Union members.
Discriminated Unions offer an elegant solution for handling all possible object variations reducing the risk for runtime errors. Typescript uses Discriminated Unions to infer types based on a property called the Discriminator Property.
The below example does not use a Discriminated Union. You can see how your code may have to perform some additional safety checks to determine the shape of the data. Additionally, we must mark majority of our properties as optional due to the multiple possible states for an ApiResponse to be in.
interface ApiResponse<T = any> {
status: 'success' | 'error'
code: number
data?: T
error?: {
message: string
}
}
In this instance, Typescript will not be able to provide proper type safety. Typescript would allow you to access properties which may not be present at runtime.
const handleApiResponse = (response: ApiResponse) {
switch(response.status) {
case 'success':
// When status is success, error shouldn't exist.
console.log(response.error?.message)
break
case 'error':
// When status is error, data shouldn't exists
console.log(response.data)
break
default:
break
}
}
Let’s fix this with a Discriminated Union. We have a Union type called ApiResponse
composed of the Union between ApiSuccess
and ApiError
. In this case, each member has a literal type which we can use as the discriminator.
interface ApiSuccess<TData = any> {
status: 'success',
code: number
data?: TData
}
interface ApiError {
status: 'error',
code: number
error: {
message: string
}
}
type ApiResponse = ApiSuccess | ApiError
const handleApiResponse = (response: ApiResponse) => {
switch(response.status) {
case 'success':
// ERROR: error does not exist on type ApiSuccess
console.log(response.error)
break
case 'error':
// ERROR: data does not exist on type ApiError
console.log(response.data)
break
default:
break
}
}
In the handleApiResponse
function, the switch expression is the discriminated property
which allows Typescript to infer the union member type for each case. When this happens, Typescript is smart enough to tell us when we try to access properties which do not exist on the member. This improves our developer experience and code readability. Pretty neat.
TypeScript discriminated unions provide flexibility for working with object types and object variations. The improved type safety makes discriminated unions a must have in your developer toolkit.
Read more about everyday types with Typescript.