What is the best way to implement a comprehensive switch case in Typescript using string enums that are referencing other string enums?

I am faced with a challenge where I have a subset of values from one enum that I need to switch case across in TypeScript. Here's an example to illustrate my dilemma:

const enum Fruit {
    APPLE = 'Apple',
    BANANA = 'Banana',
    ORANGE = 'Orange'
}

const enum AvailableFruit {
    APPLE = Fruit.APPLE,
    BANANA = Fruit.BANANA
}


function processFruit(fruitSelection: string): boolean {
    const availableFruit: AvailableFruit = (fruitSelection as unknown) as AvailableFruit;
    switch (availableFruit) {
        case AvailableFruit.APPLE:
            return true;
        case AvailableFruit.BANANA:
            return true;
        default:
            return handleInvalidFruit(availableFruit);
    }
}

function handleInvalidFruit(availableFruit: never): never {
    throw new Error();
}

While attempting to compile this code, the default case in the switch statement throws the following error message:

Argument of type 'AvailableFruit' is not assignable to parameter of type 'never'.

If the switch was done directly on the Fruit enum instead of AvailableFruit, it would function correctly.

After reviewing the TypeScript documentation, I cannot find a clear explanation as to why the compiler treats these two enums differently even though they have the same string values.

Answer №1

I will simplify the example a bit, but the underlying issues remain the same.


In TypeScript, there are enums where each enum value is defined as either a string literal or a numeric literal type. These types of enums act similarly to union types, which can be narrowed down using type guards. This type of enum is known as a union enum:

// union enum
const enum SupportedColor {
    BLUE = "Blue",
    YELLOW = "Yellow"
}

// no error here
function doSomething(supportedColor: SupportedColor): boolean {
    switch (supportedColor) {
        case SupportedColor.BLUE:
            supportedColor // SupportedColor.BLUE
            return true;
        case SupportedColor.YELLOW:
            supportedColor // SupportedColor.YELLOW
            return true;
    }
}

In the above code, the compiler identifies that the switch statement covers all possible cases because the type of 'supportedColor' can be narrowed based on control flow analysis. The type SupportedColor represents the union SupportedColor.BLUE | SupportedColor.YELLOW. Therefore, within each case block, 'supportedColor' gets narrowed down to either SupportedColor.BLUE or SupportedColor.YELLOW. After the switch statement, the compiler knows that 'supportedColor' has a type of never since both BLUE and YELLOW have been accounted for. If you uncomment the code after the switch block, the compiler will show an error in TS3.7+ stating that it is unreachable.

Thus, all code paths return a value, and the compiler does not raise any complaints.


Now, let's consider what happens when we change the enum values to be calculated instead of literals:

const enum Color {
    BLUE = 'Blue',
    YELLOW = 'Yellow',
    RED = 'Red'
}

// calculated enum
const enum SupportedColor {
    BLUE = Color.BLUE,
    YELLOW = Color.YELLOW
}

function doSomething(supportedColor: SupportedColor): boolean { // error!
    switch (supportedColor) {
        case SupportedColor.BLUE:
            supportedColor // SupportedColor
            return true;
        case SupportedColor.YELLOW:
            supportedColor // SupportedColor
            return true;
    }
    supportedColor // SupportedColor
}

Here, the enum type SupportedColor is no longer regarded as a union type. Calculated enums differ from union enums. As a result, the compiler cannot narrow down the type of 'supportedColor' in or after the switch statement. The type remains as SupportedColor throughout. Since the switch statement is not exhaustive, the compiler throws an error indicating that the function may not always return a value.


This explains why calculated enums are handled differently, as stated in the documentation regarding union enums versus calculated enums. While the docs don't elaborate on the reasons behind this design, it seems intentional according to the TypeScript team. It is suggested that if you want union enums, stick to using literal values rather than calculated ones.


I hope this clarifies things; best of luck!

Answer №2

The purpose of the given example is not entirely clear to me, but perhaps one could modify the invalidColor function signature to accept a parameter of type unknown:

function invalidColor(supportedColor: unknown): never {
    throw new Error();
}

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

Execute TypeScript on the Angular project's specified version

Is there a way to efficiently manage multiple projects on the same computer that require different versions of Angular? Can I specify the version of Angular within the package.json file to avoid conflicts? ...

TypeScript's type assertions do not result in errors when provided with an incorrect value

We are currently using the msal library and are in the process of converting our javaScript code to TypeScript. In the screenshot below, TypeScript correctly identifies the expected type for cacheLocation, which can be either the string localStorage, the ...

Manipulating arrays of objects using JavaScript

I am working with an array of objects represented as follows. data: [ {col: ['amb', 1, 2],} , {col: ['bfg', 3, 4], },] My goal is to transform this data into an array of arrays like the one shown below. [ [{a: 'amb',b: [1], c ...

Tips for associating an id with PrimeNg menu command

Within my table, I have a list of items that I would like to enhance using PrimeNg Menu for dropdown menu options. The goal is to enable navigation to other pages based on the selected item id. When a user clicks on a menu item, I want to bind the id of th ...

Tips for invoking TypeScript code from Rust WebAssembly

Currently, I am considering transitioning a slow TypeScript library (jackson-js) to WASM using rust. This particular library has various dependencies, like reflect-metadata for example. These dependencies are already created and accessible on npmjs. The ...

Elements that allow for asynchronous data submission without requiring a traditional submit button

Hey there, I could really use some help with this puzzle I'm trying to solve. Here's the situation: <ul> <li>Number: <span id="Number">123</span></li> </ul> I want to set up a connection between t ...

Angular Form: displaying multiple hashtags within an input field

Utilizing Angular CLI and Angular Material, I have created a form to input new hashtags. I am facing difficulty in displaying previously added hashtags in the input field. Below is the code I have written: form.component.html <form [formGroup]="crea ...

Transferring data types to a component and then sending it to a factory

I have been grappling with creating a factory method using Angular 2 and TypeScript. However, my attempts have hit a roadblock as the TSC compiler keeps throwing an unexpected error: error TS1005: ',' expected. The issue arises when I try to pa ...

Encountering TS2344 error when referring to the Component Ref in Vue.js during typing operations

I received a component reference on my FormCheckbox component from a patternlib. When I tried to incorporate the component into my own TestComp component, I encountered this TypeScript error that left me puzzled: TS2344: Type '{ name: string; mixins: ...

An error has occurred in Angular: No routes were found that match the URL segment 'null'

I've recently developed a simple Angular page that extracts an ID (a guid) from the URL and uses it to make an API call. While I have successfully implemented similar pages in the past without any issues, this particular one is presenting challenges w ...

Using Typescript and webpack to detect variables that are defined in the browser but not in Node environment

My goal is to create a package that can be used on both servers and clients with minimal modifications required. Some libraries are available in Node but not in a browser, while others are accessible in a browser but not in Node. For instance, when utili ...

Switch the following line utilizing a regular expression

Currently, I am facing a challenge with a large file that needs translation for the WordPress LocoTranslate plugin. Specifically, I need to translate the content within the msgstr quotes based on the content in the msgid quotes. An example of this is: #: . ...

Create a function that retrieves the value associated with a specific path within an object

I want to implement a basic utility function that can extract a specific path from an object like the following: interface Human { address: { city: { name: string; } } } const human: Human = { address: { city: { name: "Town"}}}; getIn< ...

The npm warning indicates that the file node_modules/.staging/typescript-8be04997/lib/zh-CN/diagnosticMessages.generated.json does not exist due to an ENOENT error

I am currently in the process of running npm install on a Linux machine where I do not have sudo access. Unfortunately, I have a machine-specific package.json and package-lock.json that cannot be changed. However, I encountered some errors during the insta ...

Is it possible for ko.mapping to elegantly encompass both getters and setters?

Exploring the fusion of Knockout and TypeScript. Check out this code snippet: class Person { public FirstName:string = "John"; public LastName: string = "Doe"; public get FullName(): string { return this.FirstName + " " + this.Las ...

Angular has its own unique way of handling regular expressions through its TypeScript

Considering the creation of an enum to store various regex patterns in my application for enhanced code reuse. For example: export enum Regex { ONE_PATTERN = /^[example]+$/g, ANOTHER_PATTERN = /^[test]{5,7}$/g } However: Encountering the TS90 ...

Angular Universal involves making two HTTP calls

When using Angular Universal, I noticed that Http calls are being made twice on the initial load. I attempted to use transferState and implemented a caching mechanism in my project, but unfortunately, it did not resolve the issue. if (isPlatf ...

Express: Every declaration of 'user' must have the same modifiers

In my application, I am utilizing both express and passport. Within these packages, there is a user attribute within the Request interface. Currently, the express package has a user attribute in the request object, such as req.user, but no additional prope ...

What is the best way to pass the modal dialog parameter sent from HTML to the TypeScript page?

I have implemented a dialog window using Angular where it opens by passing a parameter in the HTML side along with a transaction. To further enhance the functionality of my page, I want to incorporate the fab button. However, I am facing a challenge in sen ...

Tips for implementing collapsible functionality that activates only when a specific row is clicked

I need to update the functionality so that when I click on a row icon, only that specific row collapses and displays its data. Currently, when I click on any row in the table, it expands all rows to display their content. {data.map((dataRow ...