What is the best way to search through string enums in TypeScript strict mode?

Implementing TypeScript 2.8.4 in strict mode

Here's an enum example:

export enum TabIndex {
  Editor = 'editor',
  Console = 'console',
  Settings = 'settings',
  Outputs = 'outputs'
}

I am creating a Map from this enum:

tabs = new Map(<[string, string][]>Object.keys(TabIndex).map((key: any) => [TabIndex[key], key]));

Later on, I am trying to access a specific member from the Map using a query parameter:

const tabParam = this.urlSerializer.parse(this.location.path()).queryParams.tab; // can be 'editor', 'console', 'settings', etc.

Then I check if the Map has that particular key:

if (this.tabs.has(tabParam)) {
  this.selectTab(TabIndex[this.tabs.get(tabParam)!]);
}

Despite this, TypeScript continues to throw an error:

Element implicitly has an 'any' type because index expression is not of type 'number'.

Although I am aware that the index type is a string, I want to stick with it due to enums supporting string values. Any suggestions on how to resolve this TypeScript error?

After some investigation, I came across a workaround mentioned in this comment utilizing keyof typeof:

const tabParam: keyof typeof TabIndex = this.urlSerializer.parse(this.location.path()).queryParams.tab;

However, using this approach results in another TypeScript error:

Type 'string' is not assignable to type '"Editor" | "Console" | "Settings" | "Outputs"'

Answer №1

It seems like the issue lies in the type declaration of your map, which is currently set as Map<string, string>. This error is a result of how you initialized the map using

new Map(<[string, string][]> ...)
.

The problem arises when you attempt to retrieve a value from the map using the get method with a key. The map's type dictates that the values are strings, leading to the error message:

Type 'string' is not assignable to type '"Editor" | "Console" | "Settings" | "Outputs"'

Subsequently, in the following code snippet, you pass the retrieved value to TabIndex, which expects one of the specific values

"Editor" | "Console" | "Settings" | "Outputs"
, as indicated by the error message.

To address this issue, you should adjust the type of the map so that TypeScript knows the possible string literal values. This can be achieved by utilizing 'union types of those specific string literals' extracted from the TabIndex definition.

Modify the map creation as follows:

const tabs = new Map(<[string, keyof typeof TabIndex][]>Object.keys(TabIndex).map((key: any) => [TabIndex[key], key]));

This adjustment ensures that the map values, when passed to TabIndex[xxxx], will always be a recognized string literal.

Answer №2

Issue arises in the line where typeParam is defined, as a string (probably queryParam.tab is a string) is being assigned to a keyof typeof TabIndex. Since a string can hold any value, it cannot be assigned to a variable that only accepts four specific values.

However, if you are certain that tab will always be as expected, you can make an assertion:

type TabIndexKey = keyof typeof TabIndex;
const tabParam: TabIndexKey = this.urlSerializer.parse(this.location.path()).queryParams.tab as TabIndexKey.

If queryParams.tab is of type any, the solution remains the same.

UPDATE: To resolve the issue of not using implicit any, enums' index expressions should utilize numbers instead of strings. This means:

TabIndex['editor']; // error with not implicit any
TabIndex[0]; // correct

Hence, it is advised to alter the enum definition and tabs map to use numbers instead of strings:

export enum TabIndex {
  Editor = 0,
  Console = 1,
  Settings = 2,
  Outputs = 3
}

const tabs = new Map(<[string, number][]>Object.keys(TabIndex).map((key: any) => [key, TabIndex[key]]));

Although tabs technically contains both strings and numbers, we can overlook this fact since only string keys are needed in this case.

Now,

TabIndex[this.tabs.get(tabParam)!]
functions correctly as this.tabs.get returns a number.

Hope this explanation proves helpful.

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

Introducing a variety of services into the system

From my understanding, services must be provided and injected, meaning each service needs to be placed inside the constructor like this: constructor (private a: AService, private B: BService) {} In my scenario, I have multiple services that all follow th ...

Creating a new endpoint within the Angular2 framework using typescript

I am brand new to Angular2 and I would like to streamline my API endpoints by creating a single class that can be injected into all of my services. What is the most optimal approach for achieving this in Angular2? Should I define an @Injectable class sim ...

Sending user input data from a React text field to a function as arguments

Below are input fields, from which I need to retrieve the entered values and pass them to the onClick event of the button displayed below. <input type="text" style={textFieldStyle} name="topicBox" placeholder="Enter topic here..."/> <input type=" ...

Guide on utilizing TypeScript declarations imported as `* as`

Currently, I am working with react-icons and attempting to import all icon definitions using the syntax import * as Icons from 'react-icons/fi'. However, I am facing a dilemma on how to create a type that must be one of the types exported from Ic ...

Using Cypress fixtures with TypeScript

After transitioning from using Cypress with Javascript specs to Typescript, I encountered a challenge in working with Fixtures. In Javascript, the approach below worked; however, I faced difficulties when switching to Typescript. Fixture JSON file: I sto ...

Prettier mandates that single-line if-statements without curly braces must be written on the same line

Having recently delved into the EsLint documentation, I've adopted the curly rule set to warning for instances of multiple or nested rows of statements within conditionals. "rules": { "curly":["warn", "multi-or-nes ...

Leverage the power of Typescript to flatten a JSON model with the help of Class

Currently, I'm exploring how to utilize the class transformer in TypeScript within a Node.js environment. You can find more information about it here: https://github.com/typestack/class-transformer My goal is to flatten a JSON structure using just on ...

Incorporating TypeScript into a React-Native Expo development venture

While attempting to integrate TypeScript into a React-Native Expo project, I encountered an error when renaming a file from abc.js to abc.tsx: I have been following the instructions provided at: https://facebook.github.io/react-native/blog/2018/05/07/u ...

DateAdapter not found within Angular/Material/Datepicker - Provider not available

I need assistance with: angular / material / datepicker. My test project is running smoothly and consists of the following files: /src/app/app.module.ts import { BrowserModule } from '@angular/platform-browser'; import { NgModule } from ' ...

Strategies for extracting information from the database

I have a pre-existing database that I'm trying to retrieve data from. However, whenever I run a test query, it always returns an empty value: { "users": [] } What could be causing this issue? entity: import {Entity, PrimaryGeneratedColumn, Col ...

How can I customize a currency directive in AngularJS using filters?

My goal is to enhance user experience by allowing input in custom currency values like '1.5M' instead of 1500000, and '1B' instead of 1000000000 on an input form dealing with large numbers. To achieve this, I've created a FormatSer ...

Having trouble linking tables to Node.js with TypeScriptyntax?

I am facing an issue with mapping multiple entities using sequelize. I keep encountering the error message " Error: Profesor.hasOne called with something that's not a subclass of Sequelize.Model". How can I resolve this issue? Below is the code for t ...

Sundays and last days are excluding React-big-calendar and dayjs longer events from being displayed

I've encountered a bug in my calendar view implementation. Long events are not displaying on Sundays or the ending day. Please refer to this image for reference: https://i.stack.imgur.com/V0iis.png Event details: Start time: Mon Aug 07 2023 15:44:00 ...

Encountered 'DatePickerProps<unknown>' error while attempting to develop a custom component using Material-UI and react-hook-form

Currently, I'm attempting to create a reusable component using MUI Datepicker and React Hook Form However, the parent component is throwing an error Type '{ control: Control<FieldValues, object>; name: string; }' is missing the follow ...

Running JavaScript code when the route changes in Angular 6

Currently, I am in the process of upgrading a website that was originally developed using vanilla JS and JQuery to a new UI built with Angular and typescript. Our site also utilizes piwik for monitoring client activity, and the piwik module was created i ...

Implementing strict validation for Angular @Input by allowing only predefined values

I am facing an issue where I receive a string value as a parameter in a component, but I want to restrict the possible values that can be passed as a parameter, similar to using an enum. Currently, I have: @Input() type: string = ''; However, ...

It is not possible to access the Angular component using routing capabilities

I'm facing an issue with my Angular routing setup. I have two components, "view-emp" and "edit-emp", but only "edit-emp" is accessible for navigation. When I try to click on the link to navigate to "view-emp", nothing happens and I stay on the home sc ...

What are the circumstances under which JavaScript GCP libraries return null values?

My current project involves working with GCP and Firebase using typescript. I have been utilizing the provided libraries, specifically version 8 of Firebase, and have encountered some unexpected behavior. For instance (firebase, ver. 8.10.1) import 'f ...

What is the process of declaring a global function in TypeScript?

I am looking to create a universal function that can be accessed globally without needing to import the module each time it is used. The purpose of this function is to serve as an alternative to the safe navigation operator (?) found in C#. I want to avoi ...

Django and Angular combine to create a floral mapping feature that allows users to easily return to their task list

I am looking to arrange the output from the flower library (/api/tasks) into a list of objects. The current response includes multiple objects, but lacks a "list wrapper", making it difficult to iterate over. API: An example of the return is as follows: H ...