Typescript: Object Keys from Array
September 28, 2020
While creating a breakpoints utility on a styled-components theme (something like theme.breakpoints.down('sm')
) I felt it would be valuable to have the size options strongly typed using a string array of possible sizes. Let’s set up our starting point:
const sizes = ['xs', 'sm', 'md', 'lg', 'xl']
const values = {
xs: 0,
sm: 600,
md: 960,
lg: 1280,
xl: 1920,
}
At first that may seem unnecessary because an interface for values
defining the possible key-value pairs would be the right way to do it, but that won’t work for my use case.
I plan on referencing that array of possible sizes in each breakpoints
method since the only possible parameters should be one of those strings.
Is this overkill for a media query utility? Maybe 🤷♂️
String array to string literals
First we need to convert the sizes
array to a string literals array. We can do this with const assertions
as of Typescript 3.4. Essentially, this will assert the literal value of the array instead of widening the type to string[]
.
const sizes = ['xs', 'sm', 'md', 'lg', 'xl']
// type: string[] ❌
const sizes = ['xs', 'sm', 'md', 'lg', 'xl'] as const
// type: readonly ["xs", "sm", "md", "lg", "xl"] ✅
It’s important to do this because when we create the type for values
we don’t want it to allow just any string as a key - the string must exist in sizes
Mapped type for the values
object
For the values
object, we’re going to create a mapped type using the in
keyword and our sizes
array. This will iterate over each item in sizes
to create the keys then set the value type for those keys as number
. The syntax for this one can be a bit difficult to read at first.
type Values = {
[K in typeof sizes[number]]: number
}
// which is equivalent to:
type Values = {
xs: number
sm: number
md: number
lg: number
xl: number
}
// ✅
K
is the type parameter - you can think of it as Key
, but you’ll typically see it written as K
. Then (typeof sizes)[number]
is an indexed access type, which will get the resulting type from indexing typeof sizes
with an index of type number
. A more readable translation may be “create a key for each item in sizes
then set the key value as type number
.”
If we hadn’t used const assertion
on sizes
the type would be very general and our mapped Values
type would be equivalent to:
type Values = {
[x: string]: number
}
// ❌
Conclusion
Now we have a thorough type to apply to our values
object that is based on the sizes
string array. Here’s our tidy, finished code all together:
const sizes = ['xs', 'sm', 'md', 'lg', 'xl'] as const
type Values = { [K in typeof sizes[number]]: number }
const values: Values = {
xs: 0,
sm: 600,
md: 960,
lg: 1280,
xl: 1920,
}
If we were to add another value to values
whose key does not exist in sizes
Typescript would give the following error:
const values: Values = {
// ...
xxl: 2500,
}
// Type '{ xs: number; sm: number; md: number; lg: number; xl: number; xxl: number; }' is not assignable to type 'Options'.
// Object literal may only specify known properties, and 'xxl' does not exist in type 'Options'.
Let's talk
I'm currently looking for a remote full-time React Engineer position. If you know of any feel free to reach out!
cody@codyarose.com