Reputation: 467
I'm building a dropdown-style component the value and options are distinct props. I'm hoping to use Typescript to ensure that the value is a valid option. While I could do this check at runtime, I feel like this is something I should be able to do in TS.
Is this possible?
interface Props<N extends number> {
count: N,
options: Array<N | number> // Tuples don't work here, since this can be any length
}
const Component = <N extends number>({count, options} : Props<N>) => {
// We could do a runtime check to verify that `count` is in `options`, but I'd rather do the check earlier if possible
}
Component({count: 5, options: [1,2,3]}) // This should be an error
Upvotes: 1
Views: 219
Reputation: 23925
The idea is to infer both the literal types of count
and options
into the generic types N
and O
. This is trivial for N
, but for O
we need to hint to the compiler to infer a tuple type of number literals instead of just number[]
. We can achieve this by wrapping O
into a variadic tuple type which looks like [...O]
.
To achieve the validation, we can use a conditional type to check if N
is a member of O[number]
. If it is not a member, the conditional resolves to never
leading to the error as nothing can be assigned to never
.
interface Props<N extends number, O extends readonly number[]> {
count: N;
options: N extends O[number] ? readonly [...O] : never;
}
const Component = <N extends number, O extends readonly number[]>({
count,
options,
}: Props<N, O>) => {};
This works fine, as long as the array passed as options
is a tuple type.
Component({ count: 5, options: [1, 2, 3] }); // error
Component({ count: 5, options: [1, 2, 3, 5] }); // ok
Component({ count: 5, options: [] as number[] });
// ok, all the elements in options are number and 5 does extend number :/
If options
is an array type, its elements are of type number
and 5
does extend number
which ultimately passes the type checking.
Upvotes: 1