How to create a type guard for string union types in TypeScript
- Peter ParkesCo-founder
String union types are convenient, but creating type guards for them without repetition isn’t immediately obvious. This post explains how to create DRY type guards for string union types in TypeScript.
We use string union types in various places throughout Qualdesk. They’re easy to read, easy to update, and because they’re strings, they’re useful when you need to specify things like CSS classnames.
To refresh your memory, a string union type looks like this:
type ColorKey = 'red' | 'orange' | 'yellow' | 'green' | 'blue' | 'purple'
If we want to:
- check that a string is a
ColorKey
, and - narrow the type of the string to
ColorKey
we need to write a user-defined type guard.
For example, a simple type guard for ‘red’ might look like this:
function isRed(colorKey: ColorKey): colorKey is 'red' {
return colorKey === 'red'
}
What if we want to check if a string is any one of the valid color keys?
We could do something like this:
function isColorKey(colorKey: string): colorKey is ColorKey {
switch (colorKey) {
case 'red':
case 'orange':
case 'yellow':
case 'green':
case 'blue':
case 'purple':
return true
default:
return false
}
}
but then we’ve just repeated the entire type definition in the type guard function.
In this situation, if we add a new color, we have to update both the type and the type guard.
There’s an alternative.
First, we need to define an array of colors:
const COLORS = ['red', 'orange', 'yellow', 'green', 'blue', 'purple'] as const
You’ll notice that there’s as const
at the end of this definition. This defines COLORS
as a readonly array consisting of precisely the values in the declaration.
This is important in the next step, definining the ColorKey
type itself:
type ColorKey = typeof COLORS[number]
What does this do? It tells the compiler that ColorKey
is the type of any of the elements in COLORS
. An ‘any of’ type is a union type, and so we end up with the ColorKey
type definition exactly as we did above.
If we didn’t cast the COLORS
array as const
, the type of each of the elements in COLORS
would simply be string
, and so we’d end up with ColorKey === string
: not very helpful.
Finally, we can rewrite the type guard using the COLORS
array:
function isColorKey(colorKey: string): colorKey is ColorKey {
return COLORS.includes(colorKey as ColorKey)
}
Now we have a DRY type guard. If we want to add a new color, we just add it to the array, and the type guard will recognize it immediately.