Employing `enum` to pass as a parameter in a function

I have encountered a problem in Typescript that I can't seem to solve. I need help figuring it out. I am working with an API that returns enum values, and I modeled them in Typescript like this:

enum condition = {NEW, USED}

However, when trying to work with data from the API, I need to specify them as typeof keyof condition, and accessing condition[0] (which is equivalent to condition[condition[NEW]]) results in the error message

Argument of type 'string' is not assignable to parameter of type '"NEW" | "USED"'.(2345)

The Typescript export for the condition object looks like this:

var condition;
(function (condition) {
    condition[condition["NEW"] = 0] = "NEW";
    condition[condition["USED"] = 1] = "USED";
})(condition || (condition = {}));
;

This means that condition.NEW is 0 and condition[0] is NEW. I tried forcing the type by passing it with the as keyof typeof condition like this:

enum condition {NEW, USED};

function test(param: keyof typeof condition) {
    console.log(param);
}

test(condition[condition.NEW]); // Fails linting, should pass
test(condition[condition.NEW] as keyof typeof condition); // Lint success
test('a' as keyof typeof condition); // Lint success, should fail

(Link to the playground: Playground )

But this method seems like a workaround at best, as it ignores the type being passed to it. I'm concerned that passing an invalid string won't be properly detected. How can I make Typescript validate test(condition[condition.NEW]); as valid, and

test('a' as keyof typeof condition);
as invalid?

Answer №1

Encountering a limitation in TypeScript related to numeric enums, the feature has been requested on microsoft/TypeScript#38806 and microsoft/TypeScript#50933. Numeric enums have reverse mappings where you can retrieve the string key by indexing into the enum object with the numerical value of the enum. However, the type system does not strongly encode this reverse mapping; it simply provides an index signature for the enum object with a property type of `string`. This means that TypeScript only recognizes `Condition[0]` as type `string`, which may not be sufficient for your needs.

Until more robust reverse typings are added, a workaround is needed. You can create a utility function to provide such typings:

function strongReverseEnumMapping<const T extends Record<keyof T, PropertyKey>>(e: T) {
    return e as
        { readonly [K in string & keyof T]: T[K] } & 
        { readonly [K in keyof T as T[K] extends number ? T[K] : never]: K };
}

The `strongReverseEnumMapping()` function returns the input at runtime but asserts a stronger type, creating an intersection between the remapped numeric values to keys and keys to values within the enum object.

You can then rename the original enum, pass it to the helper function, and obtain a stronger-typed enum:

enum _Condition { NEW, USED };
const Condition = strongReverseEnumMapping(_Condition);   
/* const Condition: {
  readonly NEW: _Condition.NEW;
  readonly USED: _Condition.USED;
} & {
  readonly 0: "NEW";
  readonly 1: "USED";
} */
type Condition = _Condition;

Now, `Condition` is recognized to have `"NEW"` at key `0` and `"USED"` at key `1`, enabling operations like `Condition[Condition.NEW]` to work as expected:

const c = Condition[Condition.NEW]
//    ^? const c: "NEW";

Access the Playground link to code

Answer №2

If the API utilizes strings to represent the enum values instead of numbers, it may be more efficient to define a string enum as shown below:

enum Status {
    ACTIVE = 'ACTIVE',
    INACTIVE = 'INACTIVE'
};

function verify(status: Status) {
    console.log(status);
}

verify(Status.ACTIVE);
verify(Status[Status.ACTIVE]);
verify(Status['ACTIVE']);

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

Utilizing the Composition Root concept in a TypeScript Express application

I am trying to grasp the concept of implementing a composition root in a project. Based on my research, improper usage of the composition root (such as referencing it in multiple places within your application code) can lead to the service locator antipat ...

Show detailed information in a table cell containing various arrays using AngularJS

After integrating d3.js into my code, I now have an array with key-value pairs. Each team is assigned a key and its corresponding cost is the value. When I check the console log, it looks like this: Console.log for key and value Rate for current month [{ ...

In what way can I ensure that both parameters of a function match a particular Union type?

My goal is to develop a function that takes two parameters. The first parameter is a union type, and the second parameter's type depends on the type of the first one. For instance: type Fruit = "Orange" | "Apple" | "Banana"; function doubleFruit< ...

mat-autocomplete Show user-friendly names while storing corresponding IDs

I am currently utilizing a Mat-autocomplete feature that allows for loading a list of "users". Within the autocomplete functionality, I aim to exhibit the username while retaining the selected user ID value. Originally, I had: this.allFruits = val.map( ...

Ensuring the correct type of keys during Object.entries iteration in TypeScript

When using Object.entries(), it returns the correct value types, but the keys are of type string[], which is incorrect. I want TypeScript to recognize my keys correctly. I attempted to use as const on the object, but it did not have any effect. Is there a ...

Link the chosen selection from a dropdown menu to a TypeScript object in Angular 2

I have a form that allows users to create Todo's. An ITodo object includes the following properties: export interface ITodo { id: number; title: string; priority: ITodoPriority; } export interface ITodoPriority { id: number; name ...

Uncertain about the distinction between reducers and dispatchers when it comes to handling actions

I'm feeling a bit confused regarding reducers and dispatchers. While both receive actions as parameters, it doesn't necessarily mean that the actions I use in my dispatchers are the same as those used in my reducers, correct? For example, if I h ...

How can express.js be properly installed using typescript?

Currently, I am in the process of setting up a new project that involves using express.js with typescript integration. Would it suffice to just install @types/express by running the following command: npm install @types/express Alternatively, do I also ...

Trigger a state update in the parent component by clicking on a button within the child component using React

I am facing an issue where I have a state in the parent component that I want to update from an onClick event in the child component, but the state is not updating as expected. What could be causing this problem? Another Dilemma I need to display a popup ...

If the parent is optional, types cannot be obtained

I'm trying to set the type ShowOptions as "ALL", "ACTIVE", or "DRAFT" However, I encountered an error saying TS2339: Property show does not exist on type Does anyone have suggestions on how I can extract the ShowOptions ...

Losing context when values are passed as typed values

As someone who is still relatively new to typescript, I've been navigating my way through it fairly well so far. However, I recently encountered an issue that has stumped me. My current challenge involves passing data within my React application in t ...

How to customize the default color palette in Bootstrap 5 for a Next.js project using Sass?

After successfully loading and implementing Bootstrap in my next.js app, I have been struggling for several days to customize the default color scheme. In my global.scss file: @import "../node_modules/bootstrap/scss/bootstrap"; $primary:#f3ced6 ...

Collecting user data input when a button is clicked

I am a beginner in the world of Angular and currently working on developing a simple to-do list application. My goal is to store the description, start time, and end time input by the user as an object in an array when the user clicks a button. Initially, ...

Exploring the insights into React-hook-form Controller

I've inherited a website project that utilizes an unfamiliar method in the Controller part of react-hook-form. Here is an example of the code snippet: const CheckboxController = (props: CheckboxProps) => { return ( <Wrapper> < ...

Error encountered during Typescript compilation: The attribute 'raw' is not found within the context of the entity 'e' in express

In many instances, I have noticed that people use express.raw() or express.raw({type: 'application/json'}) as middleware in their requests... but is .raw() a legitimate method in Express? I am currently working with TypeScript and using Express ...

mongoose memory leak attributed to jest

UPDATED 2020-09-14 I've encountered an issue with a test case I wrote. While the testcase passes, it raises a complaint about improper teardown and an open connection. Can anyone help identify the problem: Approach to Solving the Issue - Memory Leak ...

Display clickable buttons beneath the Material-UI Autocomplete component

I want to place buttons ("Accept" and "Cancel") below the MUI Autocomplete element, and I am trying to achieve the following: Limit the Autocomplete popover height to display only 3 elements. To accomplish this, pass sx to ListboxProps Ensure that the b ...

Encasing a drop-down menu within a personalized container

I am looking to create a custom HTML element that mimics the behavior of the native <select> element but also triggers a specific update function whenever an attribute or child node is modified. This is essential for incorporating the bootstrap-selec ...

Waiting for the function to complete within an if statement

When attempting a test, I encountered an issue where the function does not wait for the database request to be made. It seems like the function initially returns undefined, and only after the request is completed it returns true or false causing the test t ...

Is there a way to view the console in a released apk?

Currently working with Ionic and in need of exporting a release APK to be able to monitor the console for any potential issues. I am aware that using 'ionic cordova run --device' allows me to view the console, but it only shows a debug APK. Is t ...