DEV Community

yossarian
yossarian

Posted on • Originally published at catchts.com

2 2

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.

Hostinger image

Get n8n VPS hosting 3x cheaper than a cloud solution

Get fast, easy, secure n8n VPS hosting from $4.99/mo at Hostinger. Automate any workflow using a pre-installed n8n application and no-code customization.

Start now

Top comments (0)

SurveyJS custom survey software

JavaScript Form Builder UI Component

Generate dynamic JSON-driven forms directly in your JavaScript app (Angular, React, Vue.js, jQuery) with a fully customizable drag-and-drop form builder. Easily integrate with any backend system and retain full ownership over your data, with no user or form submission limits.

Learn more

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay