DEV Community

yossarian
yossarian

Posted on • Originally published at catchts.com

Handle Array.prototype.includes in TypeScript

If you are working with typescript, sooner or later you will encounter an issue with Array.prototype.includes

const PROPS = ['a', 'b', 'c'] as const; PROPS.includes('d') // error const includes = (elem: string) => PROPS.includes(elem) // error 
Enter fullscreen mode Exit fullscreen mode

I don't want to dig into type theory problem of this issue. I just will provide you with curried generic solution.

const PROPS = ['a', 'b', 'c'] as const; const withTuple = < List extends string[] >(list: readonly [...List]) => (prop: string): prop is List[number] => list.includes(prop) const includes = withTuple(PROPS); const result = includes('d') declare let str: string if (includes(str)) { str // "a" | "b" | "c" } 
Enter fullscreen mode Exit fullscreen mode

However, it is still not cool. Our function works only with strings. What if we have a list of numbers or other primitives ?

First of all, we need to create utility type which will be able to convert literal type to to more wider type. I mean, it should convert literal type of 42 to number

type Primitives = | string | number | bigint | boolean | symbol | null | undefined type InferPrimitive<T, P> = P extends any ? T extends P ? P : never : never; type Inference<T> = InferPrimitive<T, Primitives> { type _ = Inference<2 | 's'> // stirng | number type __ = Inference<42> // number } 
Enter fullscreen mode Exit fullscreen mode

Now we can use our type with curried function

type Primitives = | string | number | bigint | boolean | symbol | null | undefined type InferPrimitive<T, P> = P extends any ? T extends P ? P : never : never; type Inference<T> = InferPrimitive<T, Primitives> { type _ = Inference<2 | 's'> // stirng | number type __ = Inference<42> // number } const PROPS = ['a', 'b', 'c'] as const; const withTuple = < List extends Primitives[] >(list: readonly [...List]) => (prop: Inference<List[number]>): prop is Inference<List[number]> & List[number] => list.includes(prop) const includes = withTuple(PROPS); includes(2) // expected error includes(['str']) // expected error const result = includes('d') // ok declare let str: string if (includes(str)) { str // "a" | "b" | "c" } 
Enter fullscreen mode Exit fullscreen mode

As you might have noticed, TS allows you to call includes only with strings.

That's all.

Top comments (0)