It is impossible to search within a read-only array union

Is there a way to search for an element within a readonly array union in TypeScript?

const areas = {
  area1: {
    adjacencies: [2, 3, 4, 5]
  },
  area2: {
    adjacencies: [6, 7, 8]
  }
} as const;
let area: keyof typeof areas;
if (Math.random() < 0.5) {
  area = "area1";
} else {
  area = "area2"
}

// Argument of type 'number' is not assignable to parameter of type 'never'
areas[area].adjacencies.includes(3);

I also tried indexOf, but it didn't work either. And I found the includes type is

ReadonlyArray<T>.includes(searchElement: never, fromIndex: number | undefined): boolean 
.

I suppose the includes type should be the union of the elements of two readonly arrays, like demonstrated below:

const areas = {
  area1: {
    adjacencies: [2, 3, 4, 5]
  },
  area2: {
    adjacencies: [6, 7, 8]
  }
} as const;

type ValueOf<T extends object> = T[keyof T];
type Values = ValueOf<ValueOf<typeof areas>>
type ElementUnion = Values[number];

let area: keyof typeof areas;
if (Math.random() < 0.5) {
  area = "area1";
} else {
  area = "area2"
}

areas[area].adjacencies.includes(3);

How can I apply ElementUnion to includes or indexOf methods?

Here is the playground

Answer №1

By using as const, you are instructing TypeScript to treat literals like 0 or "hello" as their exact values, rather than general types such as number or string.

Keep in mind that this approach will only work if both arrays contain the element you are looking for.

const areas = {
  area1: {
    adjacencies: [2, 3, 4, 5]
  },
  area2: {
    adjacencies: [6, 7, 8, 3]
  }
} as const;

type ValueOf<T extends object> = T[keyof T];
type Values = ValueOf<ValueOf<typeof areas>>;
type ElementUnion = Values[number];

let area: keyof typeof areas;
if (Math.random() < 0.5) {
  area = "area1";
} else {
  area = "area2"
}

areas[area].adjacencies.includes(3);

It's important to note the significance of adding a 3 to the second array in the code snippet above.

Visit Playground

Edit:

Pay attention to the function signature, which only accepts the union of the arrays as valid input.

(method) ReadonlyArray<T>.includes(searchElement: 3, fromIndex: number | undefined): boolean

Edit 2:
Example:

const areas = {
  area1: {
    adjacencies: [2, 3, 4, 5]
  },
  area2: {
    adjacencies: [6, 7, 8]
  }
} as const;

type ValueOf<T extends object> = T[keyof T];
type Values = ValueOf<ValueOf<typeof areas>>;
type ElementUnion = Values[number];

let area: keyof typeof areas;

if (Math.random() < 0.5) {
  area = "area1";
} else {
  area = "area2";
}

// When .adjacencies can be either one of the arrays
// with 'as const' in place, TypeScript considers the intersection
// (elements present in both arrays) as literal arguments
areas[area].adjacencies.includes(3);

///////////////////////////////////////////////////////////////
if (area === 'area1') {
  // In this scenario, TypeScript knows the exact elements present
  // Since the variable was declared as const, it only allows literal values from the array
  //                          v Hover over this
  areas[area].adjacencies.includes(2);
} else {
  //                          v Hover over this
  areas[area].adjacencies.includes(6);
}

// Direct access
//                         v Hover over this
areas.area1.adjacencies.includes(2);
//                         v Hover over this
areas.area2.adjacencies.includes(6);
//                         v Hover over this
areas['area1'].adjacencies.includes(2)
//                         v Hover over this
areas['area2'].adjacencies.includes(6)

Check out the playground for interactive hovers

View Playground

Answer №2

Special thanks to @zerkms for bringing up . It appears there are a couple of workarounds available.

const regions = {
  region1: {
    connections: [2, 3, 4, 5]
  },
  region2: {
    connections: [6, 7, 8]
  }
} as const;

type ValueOf<T extends object> = T[keyof T];
type Values = ValueOf<ValueOf<typeof regions>>
type ElementUnion = Values[number];

let region: keyof typeof regions;
if (Math.random() < 0.5) {
  region = "region1";
} else {
  region = "region2"
}

// initial solution using type assertion
(regions[region].connections as unknown as ElementUnion[]).includes(3); // it works!

// alternative solution attempting to utilize declaration merging
// from lib.es2016.array.include.d.ts, which seems reasonable
interface ReadonlyArray<<T> {
  includes<T>(searchElement: T, fromIndex?: number): boolean;
}

regions[region].connections is of type

readonly [2, 3, 4, 5] | readonly [6, 7, 8]
, essentially equivalent to
ReadonlyArray<2 | 3 | 4 | 5 | 6 | 7 | 8>
. I conducted an experiment:

const readOnlyArray = [1, 2, 3] as const;
readOnlyArray.includes(3); // TypeScript successfully determined T as 1 | 2 | 3

It appears that TypeScript struggles to convert a union of readonly arrays into a union of array elements, whether this is intentional or not remains unclear.

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

Enable the acceptance of various validator patterns within Angular Material 2

I'm using md-error in angular material to validate user inputs from the client side. Currently, I am trying to validate an input field that accepts two types of patterns: Pattern 1: Accept the first 9 characters as numbers followed by the 10th ch ...

Navigational menu routing with AngularJS2 using router link paths

Currently, I am working on developing a navigation menu using angularJS2. Here is the snippet from my app.component.ts: import {provide, Component} from 'angular2/core'; import {APP_BASE_HREF, ROUTER_DIRECTIVES, ROUTER_PROVIDERS, HashLocati ...

Issue with ngFor displaying only the second item in the array

There are supposed to be two editable input fields for each section, with corresponding data. However, only the second JSON from the sample is being displayed in both sections. The JSON in the TypeScript file appears as follows: this.sample = [ { "se ...

Using the TranslateService in Angular to externalize an array of strings

I am new to externalizing code. As I was working on developing a month picker in Angular, I initially had an array of months with hardcoded names in my typescript file: arr = ['Jan', 'Feb', 'Mar', 'Apr', 'May&a ...

What is the best way to fetch the id of the option that has been chosen from a bootstrap drop-down menu?

I recently created a basic drop-down list like this: https://i.sstatic.net/4Tlxx.png Here is the HTML code for it: <select class="form-control" id='0' (change)="retrieveValue($event.target)"> <option id='0'>{{ g ...

A guide to building a versatile higher-order function using TypeScript

I'm struggling with creating a function that can add functionality to another function in a generic way. Here's my current approach: /** * Creates a function that first calls originalFunction, followed by newFunction. * The created function re ...

Rxjs: accessing the most recent value emitted by an observable

As shown in the demo and indicated by the title const { combineLatest, interval, of } = rxjs; const { first, last, sample, take, withLatestFrom } = rxjs.operators; const numbers = interval(1000); const takeFourNumbers = numbers.pipe(take(4)); takeFourNu ...

Encountering a "args" property undefined error when compiling a .ts file in Visual Studio Code IDE

I've created a tsconfig.json file with the following content: { "compilerOptions": { "target": "es5" } } In my HelloWorld.ts file, I have the following code: function SayHello() { let x = "Hello World!"; alert(x); } However ...

Hold on until the page is reloaded: React

My current setup includes a React Component that contains a button. When this button is clicked, a sidePane is opened. What I want to achieve is refreshing the page first, waiting until it's completely refreshed, and then opening the sidepane. Below i ...

What is the equivalent bundle size limit in bytes for the limitBytes value in the Rollup plugin analyzer?

I recently integrated the rollup plugin analyzer into my ReactJS project. While exploring the "CI usage example" section in the documentation, I noticed a variable named const limitBytes = 1e6 const limitBytes = 1e6 const onAnalysis = ({ bundleSize }) =& ...

Can you explain the usage of the syntax in Angular marked with the @ sign, such as @NgModule, @Component, and @Injectable?

Angular utilizes specific syntax for declaring modules, components, and services, as shown in the example below: @Component({ ... }) export class AppComponent However, this syntax is not commonly seen in traditional JavaScript development. It begs the ...

Playwright failing to execute GraphQL tests due to TypeScript configuration problems

I'm facing an issue with my repo where I am running tests using Playwright against a graphQL URL. Despite configuring the tests, there is an error indicating that the environment variable defining the environment cannot be found. The repository in qu ...

The value of 'Boolean' cannot be changed as it is a constant or a read-only property and cannot be assigned

I've been working on creating a TypeScript object to handle read/write permissions in my application, but I'm stuck on an issue with variable assignment that doesn't make sense to me. Any help or guidance would be greatly appreciated. expor ...

Can NODE_PATH be configured in Typescript?

Before, I worked on my React app with ES6 and used NODE_PATH='src' to import files starting from the src folder. However, since switching to Typescript, I've realized that NODE_PATH is not supported. After some investigation, I discovered th ...

Limit the types of function parameters to only one option within a union type parameter

I have a collection of tuples that I can use to define variables: type KnownPair = ["dog", "paws"] | ["fish", "scales"]; const goodPair: KnownPair = ["dog", "paws"]; //@ts-expect-error you cannot mix them: const badPair: KnownPair = ["dog", "scales"]; I ...

Issue with routing in Angular 6 is caused by the "#" character

I am currently working on an Angular 6 project. In my app.routes, I have set it up like this. However, I am facing an issue where I can only access the route using localhost:4200/#/Student instead of localhost:4200/Student. Can you help me identify where t ...

What causes the return value of keyof to vary in this particular case?

type AppleNode = { type: 'Apple' name: string score: number } type BananaNode = { type: 'Banana' id: number score: number } type FruitNodes = AppleNode | BananaNode type fruitTest = { [P in keyof FruitNodes]: 21 } // Th ...

Utilizing the JavaScript Array.find() method to ensure accurate arithmetic calculations within an array of objects

I have a simple commission calculation method that I need help with. I am trying to use the Array.find method to return the calculated result from the percents array. The issue arises when I input a price of 30, as it calculates based on the previous objec ...

Struggles with updating app.component.ts in both @angular/router and nativescript-angular/router versions

I have been attempting to update my NativeScript application, and I am facing challenges with the new routing system introduced in the latest Angular upgrade. In my package.json file, my dependency was: "@angular/router": "3.0.0-beta.2" After the upg ...

Just a straightforward Minimum Working Example, encountering a TypeScript error TS2322 that states the object is not compatible with the type 'IntrinsicAttributes & Props & { children?: ReactNode; }'

Currently, I am immersed in a project involving React and Typescript. I am grappling with error code TS2322 and attempting to resolve it. Error: Type '{ submissionsArray: SubmissionProps[]; }' is not assignable to type 'IntrinsicAttributes ...