What is the importance of having the same data type for the searchElement in the argument for Array.prototype.includes()?

Is there an issue with my settings or is this a feature of TypeScript? Consider the code snippet below:

type AllowedChars = 'x' | 'y' | 'z';
const exampleArr: AllowedChars[] = ['x', 'y', 'z'];

function checkKey(e: KeyboardEvent) { 
    if (exampleArr.includes(e.key)) { // <-- here
        // ...
    } 
}

The TypeScript compiler displays an error that says

Argument of type 'string' is not assignable to parameter of type 'AllowedChars'
. But where am I assigning a value? The use of Array.prototype.includes only returns a boolean without any assignment. One way to silence the error is by using type assertion, such as:

if (exampleArr.includes(e.key as AllowedChars)) {}

But why is this considered correct when user input could be anything? It's puzzling why a function like Array.prototype.includes(), designed to check if an element exists in an array, needs to have knowledge about the expected input type.

This is the content of my tsconfig.json file (TypeScript version 3.1.3):

 {
    "compilerOptions": {
      "target": "esnext",
      "moduleResolution": "node",
      "allowJs": true,
      "noEmit": true,
      "strict": true,
      "isolatedModules": true,
      "esModuleInterop": true,
      "jsx": "preserve",
    },
    "include": [
      "src"
    ],
    "exclude": [
      "node_modules",
      "**/__tests__/**"
    ]
  }

If you have any insights, your help would be greatly appreciated!

Answer ā„–1

For a comprehensive discussion on Array.prototype.includes() and supertypes, please refer to microsoft/TypeScript#26255.


While technically safe to allow the searchElement parameter in Array<T>.includes() to be a supertype of T, the standard TypeScript library declaration assumes it as just T. In most cases, this assumption is valid as comparing completely unrelated types is usually not intended. However, in your case, the types are related.

There are multiple ways to address this issue. The assertion you've made might not be entirely correct since you are asserting that a string is an AllowedChars, which may not always hold true. Although it may work functionally, there could be a sense of discomfort about it.


An alternative approach is to locally override the standard library through declaration merging to accept supertypes. This method involves using conditional types to emulate a supertype constraint:

// remove "declare global" if you are writing your code in global scope to begin with
declare global {
  interface Array<T> {
    includes<U extends (T extends U ? unknown : never)>(
      searchElement: U, fromIndex?: number): boolean;
  }
}

Your original code can then operate smoothly:

if (exampleArr.includes(e.key)) {} // okay
// call to includes inspects as
// (method) Array<AllowedChars>.includes<string>(
//    searchElement: string, fromIndex?: number | undefined): boolean (+1 overload)

This modification prevents the comparison of entirely unrelated types:

if (exampleArr.includes(123)) {} // error
// Argument of type '123' is not assignable to parameter of type 'AllowedChars'.

A simpler yet accurate solution is broadening the type of exampleArr to readonly string[]:

const stringArr: readonly string[] = exampleArr; // no assertion
if (stringArr.includes(e.key)) {}  // okay

You can also achieve this more succinctly:

if ((exampleArr as readonly string[]).includes(e.key)) {} // okay

Widening to readonly string[] is acceptable, but caution is advised when widening to string[] as it may introduce risks due to covariant behavior in TypeScript. This is why using readonly is preferable.


Playground link to code

Answer ā„–2

If you're looking at two distinct types, then it's natural for them to be different.

Picture this scenario:

type A = {paramA: string};
type B = {paramB: number};

const valuesA: A[] = [{paramA: 'whatever'}];
const valueB: B = {paramB: 5};

valuesA.includes(valueB); // This condition will always return false, so it doesn't really make sense

In this specific situation, the compiler interprets AllowedChars as a completely separate type from string. You need to "cast" the received string to AllowedChars.

But why is that necessary, I'm expecting user input which could be anything.

The compiler doesn't understand the purpose of the includes check. It only recognizes that they are different types and therefore shouldn't be compared directly.

Answer ā„–3

After some experimentation, I found that switching from using .includes to .some solved my issue. You can test it out in this TypeScript playground.

const FRUITS = ["apple", "banana", "orange"] as const
type Fruit = typeof FRUITS[number]

const isFruit = (value: string): value is Fruit =>
    FRUITS.some(fruit => fruit === value)

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Upon attempting to fetch input by name, Puppeteer reported the error message: 'Node is not clickable or not an HTMLElement'

This is the structure of my HTML: <div id="divImporte"> <p class="btn01"> <input type="button" name="Enviar Tasas" value="Enviar Tasas"> </p> </div> Here are the diffe ...

"Exploring the versatility of using variables in jquery's .css

Iā€™m facing an issue where I need the rotation of a div to be based on the value stored in "anVar." This is what I currently have: function something() { $('.class').css('-webkit-transform:rotate('anVar'deg)') } The desi ...

Having trouble setting a value in a Vue.js variable

Upon assigning a value retrieved from the firebase collection, I encountered the following error message. Error getting document: TypeError: Cannot set property 'email' of undefined at eval (Profile.vue?5a88:68) Here is the code snippet in que ...

Creating customized JavaScript using jQuery for Drupal 7 Form API

Currently, I am working on developing a custom form using Drupal 7 with form API and a tableselect that includes checkboxes. I have some specific constraints for selecting the checkboxes that I intend to implement using jQuery. I created a function for han ...

Unusual layout in Next.js editor (VS Code)

My chosen formatter is prettier, but I'm encountering an issue with the way it handles simple JSX functions. Initially, my code looks like this: function HomePage() { return; <div> <h1>Hello Next.js</h1> <p> Welcome ...

A guide on extracting content from a PDF file with JavaScript

Hey there, I'm experimenting with various methods to extract content from a PDF file but so far nothing seems to be working for me. Even when I try using pdf.js, it shows an error and I can't figure out why. This is the approach I've been tr ...

Utilize AJAX to fetch and display the response from a Node JS route directly onto an HTML page

At the moment, I am retrieving client-side HTML/JS inputs (var1 and var2) which are then sent to server-side Node JS. The input values are stored in variables through an AJAX post call made to a specific route. What I want now is to define the values of v ...

How to use jQuery to locate and update the final parameter of a URL

Hello everyone, I've already done some research but couldn't find a solution that fits my needs. Can anyone assist me with this? I currently have a URL "/view/album/4/photo/1" and I am looking to remove the final parameter and replace it with so ...

Issue encountered: ENOENT - There is no file or directory located at the specified path for ... .steampath

I am encountering an issue while attempting to launch the development server on a React project that has been dormant for quite some time. After executing npm install and npm start, I encountered the following error message. Despite my efforts to manua ...

Press on any two table cells to select their content, highlight it, and save their values in variables

I have a table retrieved from a database that looks like this (the number of rows may vary): |Player 1|Player 2| ------------------- |Danny |Danny | |John |John | |Mary |Mary | My goal is to select one name from each Player column and sto ...

Managing the placement of the expanded autocomplete input in Material-UI

Whenever I use the autocomplete fields on my website, I've observed an interesting behavior. When I select multiple options, the height of the input increases significantly, causing everything below it to shift downward like this. Is there a way to m ...

Typescript's definition file includes imports that can result in errors

Occasionally, typescript may generate a definition file with code like the following, leading to compile errors: // issue.ts import { Observable } from 'rxjs'; class Issue { get data() { return new Observable(); } } // issue.d.ts class ...

How to modify the content type in an Angular.js $http.delete request

When making a $http.delete request in my Angular app, I include a config object using the following approach: return $http.delete('projects/' + projectID + '/activityTypes', {data: [{id: 2}]}) This method attaches the values from my d ...

Accessing props in react-native-testing-library and styled-components is not possible

I defined a styled-component as shown below: export const StyledButton = styled.TouchableOpacity<IButtonProps> height: 46px; width: 100%; display: flex; flex-direction: row; justify-content: center; align-items: center; height: 46px; ...

The proper method for organizing a nested array object - an obstacle arises when attempting to sort the array

I have a collection of data fetched from Web API 2.2 stored in an Angular array as objects. Each object represents a Client and includes properties like name, surname, and a collection of contracts assigned to that client. Here is the interface definition ...

Crisscrossed JavaScript Object: recursion and "intricate" conversion (using lodash)

Apologies for the complexity of this example; I've condensed it as much as possible to demonstrate what I'm aiming for I have a complicated structure that needs to be traversed and transformed based on certain conditions. Here's a brief exa ...

A guide on transferring a Vue component to a custom local library

After successfully creating components using template syntax (*vue files), I decided to move common components to a library. The component from the library (common/src/component/VButton): <template> <button ... </button> </templat ...

Trouble with Component Lifecycle (ComponentDidMount) activation while switching tabs in TabBar?

After implementing the react-native-tab-navigator library to navigate between components, I encountered an issue where the componentDidMount lifecycle method works only once. I have reached out for help by posting a query on Github and have also attempted ...

Embed API allows users to showcase a variety of charts all on one page

I am trying to incorporate two google analytics timeline charts using the embed API on a single page. However, I am facing an issue where only one chart is appearing. The first chart should display bounce rate data and the second chart should display sessi ...

Is there a way to ensure that the observer.next(something) received the value before executing observer.complete()?

I have been working on an Angular app where I am using a resolver to preload data in the component. Below is the code for the resolver: resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): void { return Observable.create(observer => { ...