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)