1

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
3
  • Pretty sure this is inexpressible. Type systems can't solve everything. Your API seems a bit strange anyway. I think the core problem is that you have 2 independent parameters that are implicitly related. Why not make the first array element the "primary" option, and do away with count altogether? Commented Jan 31, 2023 at 23:28
  • Would this approach fit your needs? Commented Jan 31, 2023 at 23:48
  • @TobiasS. That seems to work. I tried something similar, but didn’t know what to put after the ternary check. What’s that spread notation on […O]? I’ve never seen that before Commented Feb 1, 2023 at 0:10

1 Answer 1

1

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.


Playground

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.