Simple method to determine if a variable belongs to an enumeration

Among the numerous discussions on this topic, none seem to focus on developing a versatile function. This function should take an enum and a variable as input, check if the variable is part of that enum, and update the variable type if it is.

I have made progress with the following code:

// Function to check if the input is part of the enum
export function isEnumMember<T extends Record<string, K>, K>(value: unknown, enumType: T): value is K {
  return (Object.values(enumType) as Array<unknown>).includes(value);
}

// Usage:
if(isEnumMember(var1, WORK_TYPE_ENUM)) {
  // In here the type of var1 should be WORK_TYPE_ENUM
}

While this solution works, the variable var1 remains unknown or its original type like string even after the if statement.

If I change the return type to T, it doesn't work in this scenario (even when narrowing the enum to a string-valued one) (link to playground):

enum WORK_TYPE_ENUM { A = 'A', B = 'B' }

// Function to check if the input is part of the enum
export function isEnumMember<
  T extends Record<string, unknown>
>(value: unknown, enumType: T): value is T {
  return (Object.values(enumType)).includes(value);
}

// Usage:
const var1: string = 'A'

if (isEnumMember(var1, WORK_TYPE_ENUM)) {
  /**
   * This doesn't work,
   * the type of var1 is 'string & typeof WORK_TYPE_ENUM',
   * and we get the following error:
   * Type '`prefix_${string & typeof WORK_TYPE_ENUM}`' is not assignable to type '"prefix_A" | "prefix_B"'.(2322)
   *  */ 
  const foo1: `prefix_${WORK_TYPE_ENUM}` = `prefix_${var1}`
  console.log(foo1);

  // With the right type (simulated with `as`) - it does work
  const foo2: `prefix_${WORK_TYPE_ENUM}` = `prefix_${var1 as WORK_TYPE_ENUM}`
  console.log(foo2);
}

Answer №1

Understanding the inner workings of enums

Enum types in TypeScript are transpiled into JavaScript using a specific syntax that may differ from what you initially expect. Let's take a look at an example:

enum WORK_TYPE_ENUM { A = 'A', B = 'B' }

This enum is transformed into the following JavaScript code:

var WORK_TYPE_ENUM;
(function (WORK_TYPE_ENUM) {
    WORK_TYPE_ENUM["A"] = "A";
    WORK_TYPE_ENUM["B"] = "B";
})(WORK_TYPE_ENUM || (WORK_TYPE_ENUM = {}))

In essence, an enum created with the enum keyword in TypeScript becomes a plain JavaScript object with key-value pairs representing its members.

When implementing a type guard for enums, it's crucial to check if a variable corresponds to one of the object's property values. Here's a sample function I utilize:

function isEnumMember<E>(
  value: unknown, enumArg: Record<string | number | symbol, E>
): value is E {
    return (Object.values(enumArg) as unknown[]).includes(value);
}

The is keyword is utilized to reference the generic type E, which denotes the values within the enum passed to the function.

I've tested this function on scenarios posed in your query, and it appears to fulfill your requirements accurately.


Potential pitfalls of number enums

Employing number enums in TypeScript might lead to unexpected outcomes, especially when inspecting variable types after applying the earlier mentioned function. For instance:

enum WORK_TYPE_ENUM { A, B }

function isEnumMember<E>(
  value: unknown, enumArg: Record<string | number | symbol, E>
): value is E {
    return (Object.values(enumArg) as unknown[]).includes(value);
}

declare const foo: unknown;

if (isEnumMember(foo, WORK_TYPE_ENUM)) {
  foo; // <- string | WORK_TYPE_ENUM
}

This issue arises due to how TypeScript translates number enums into JavaScript equivalents.

If you wish to avoid such complications, sticking to string enums where each member's value is explicitly defined could be a safer route.


An alternative approach to enums

If the limitations of enums concern you, consider an alternate method that closely resembles using an enum in TypeScript but offers more flexibility. By defining an object with as const and generating a corresponding type, you can achieve similar functionality to enums.

Although slightly more verbose, this approach eliminates certain drawbacks associated with conventional enums and provides a familiar syntax for JavaScript developers transitioning to TypeScript.

Ultimately, both approaches have their advantages and disadvantages, so choose the one that best aligns with your coding preferences and project requirements.

Answer №2

If you're looking to ensure that the value is T matches the actual enum type T, then this is what you need:

Consider this example:

// Function to verify if the input belongs to the enum
export function isEnumMember<
  T extends Record<string, unknown>
>(value: unknown, enumType: T): value is T {
  return (Object.values(enumType)).includes(value);
}

This function can be used as follows:

// Implementation:
const var1: number = 1

if (isEnumMember(var1, WORK_TYPE_ENUM)) {
  const foo: WORK_TYPE_ENUM = var1 // valid assignment
}

Check out playground


Remember: Object.keys(MyEnum) may not yield the desired results.

enum WORK_TYPE_ENUM { A = 1, B = 2 }
console.log(WORK_TYPE_ENUM)
/*
{
  "1": "A",
  "2": "B",
  "A": 1,
  "B": 2
} 
*/
console.log(Object.values(WORK_TYPE_ENUM)) //=> ["A", "B", 1, 2]

During runtime, it's impossible to differentiate between keys and values since it's merely an object mapping one to the other.

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

Check if a string contains only special characters and no letters within them using regular expressions

My goal is to validate a string to ensure it contains letters only between two '#' symbols. For example: #one# + #two# - is considered a valid string #one# two - is not valid #one# + half + #two# - is also not a valid string (only #one# and # ...

The Next Js version "next" is at "14.2.3", indicating that the page is experiencing issues

After creating a project in Next.js version "14.2.3", I encountered this page with an error message. Surprisingly, I hadn't made any changes to my code when this occurred. https://i.sstatic.net/UcTnF3ED.png What could be the reason for this sudden i ...

Sharing an array of React objects with PHP using REACT JS

I am new to React JS and I have a question regarding sending files as a react object array to php $_FILES using axios. Any help is appreciated, thank you in advance. Here is my react code: This is the code snippet: <Row> <Col lg={4}> ...

What significance and intent does this assignment hold within the type declaration shown here?

Take a look at this code snippet: export type DataViewProps = { width: number, // in pixels height: number, // in pixels data: Data, }; export class DataView<P extends DataViewProps = DataViewProps> extends React.Component<P> { Can ...

The 'books' property cannot be found on the 'client' type

I am currently integrating the Google Book API into my project and encountering an issue while trying to add a book to a library using gapi.client. The error I keep receiving is as follows: This is the request : gapi.client.books.mylibrary.bookshelves.volu ...

Error received from Http Get request

I am currently facing an issue with my Angular component that is supposed to make a simple HTTP Get request: this.http.get<any>('http://localhost:80/userservice/login', { params: { username: this.model.username, password: this.model.passwo ...

Tips for Implementing a Button Click Sound in Ionic 2

I've noticed that many native apps have a distinct click sound when buttons are pressed. Is there a way for me to add this same feature to all buttons in Ionic 2? ...

When utilizing <number | null> or <number | undefined> within computed() or signals(), it may not function properly if the value is 0

I've encountered an issue while implementing signals and computed in my new Angular project. There's a computed value that holds an id, which is initially not set and will be assigned by user interaction. To handle this initial state, I attempte ...

Using TypeScript, define a React function component by specifying its name as a type

While working in React.js, I encountered an issue with my Function Component. When I attempted to use the function name as the type, it resulted in an error. Here is a snippet of the code: import * as React from "react"; import { render } from "react-dom ...

Exploring the integration of LeafLet into Next JS 13 for interactive mapping

I'm currently working on integrating a LeafLet map component into my Next JS 13.0.1 project, but I'm facing an issue with the rendering of the map component. Upon the initial loading of the map component, I encountered this error: ReferenceError ...

Set up a global variable for debugging

Looking to include and utilize the function below for debugging purposes: export function debug(string) { if(debugMode) { console.log(`DEBUG: ${string}`) } } However, I am unsure how to create a globally accessible variable like debugMode. Can this be ...

Utilize multiple activated modules in Angular 2

Recently, I've been exploring the new features of Angular 2 final release, particularly the updated router functionality. An interesting example showcasing the router in action can be found at this link: http://plnkr.co/edit/mXSjnUtN7CM6ZqtOicE2?p=pr ...

How to remove a variable definition in Typescript

Is there a way to reset a variable to undefined after assigning it a value? To check, I am using the Underscore function _.isUndefined(). I have attempted both myVariable = undefined and delete myVariable without success. ...

Unable to access property 'create' which is undefined

On my Ionic3 page, I am trying to trigger a modal open from within a click event function. export class HomePage { .... .... .... loadPos() { var randomLocations = Microsoft.Maps.TestDataGenerator.getLocations(5, this.map.getBounds()); ...

Exploring methods for testing React components with TypeScript's props

I am currently tackling a react-typescript project and I am looking to conduct testing on props passed to a react component using react-testing library. Here, we have the SharedDashboardUiLatestValueWidget.tsx component: export interface SharedDashboardU ...

Interface circular dependency is a phenomenon where two or more interfaces

In my MongoDB setup, I have created an interface that defines a schema using mongoose as an ODM. import mongoose, { model, Schema, Model, Document } from "mongoose"; import { IUser } from "./user"; import { IPost } from "./posts&q ...

Avoiding the pitfalls of hierarchical dependency injection in Angular 6

Too long; didn't read: How can I ensure that Angular uses the standard implementation of HttpClient in lower level modules instead of injecting a custom one with interceptors? I have developed an Angular 6 library using Angular CLI. This library expo ...

Error: Angular 4.1.3 routing is unable to locate child module within parent module

Thanks in advance for any help! Greetings, I'm encountering the following error as described in the title. Despite my attempts at troubleshooting and research, I am unable to resolve it. Below are some important files - can anyone with experience in ...

Compiling fails when creating an object literal with a generic key

I am attempting to accomplish the following task. function createRecord<T extends string>(key: T) : Record<T, T> { return {[key]: key}; // Type '{ [x: string]: T; }' is not assignable to type 'Record<T, T>' } Howe ...

Should one bother utilizing Promise.all within the context of a TypeORM transaction?

Using TypeORM to perform two operations in a single transaction with no specified order. Will utilizing Promise.all result in faster processing, or do the commands wait internally regardless? Is there any discernible difference in efficiency between the t ...