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)