Using TypeScript: Union Types for Enum Key Values

Here's the code in the TS playground too, click here.

Get the Enum key values as union types (for function parameter)

I have managed to achieve this with the animals object by using key in to extract the key as the enum ANIMALS value. However, I am struggling to find a way to do the same thing for the function parameter animalKey, where it should be something like

'cat' | 'lion' | 'parrot' | 'shark' | 'snail'
.

If anyone could help me out, it would be greatly appreciated.

enum ANIMALS {
  CAT = 'cat',
  LION = 'lion',
  PARROT = 'parrot',
  SHARK = 'shark',
  SNAIL = 'snail'
}

interface iBaseAnimal {
  name: string,
  gender: 'male' | 'female'
  wild: boolean
}

interface iShark extends iBaseAnimal {
  max_gills: number
}

interface iParrot extends iBaseAnimal {
  wing: { 
    length: 120,
    unit: 'cm'
  }
}

// DONE Overwritting property when extending base props with Omit
interface iSnail extends Omit<iBaseAnimal, 'gender'> {
  gender: 'hermaphrodite'
}

interface iAnimals {
  animals: {
    // DONE Enum values as key
    // PENDING way to interpolate proper interface value base on the enum key
    [key in ANIMALS]: iBaseAnimal
  },
  // PENDING way to get Enum values as union types (similar to [key in ANIMALS] but for function param)
  getAnimal: (animalKey:'lion', options: any) => void
}

Answer №1

Here is a unique solution using TypeScript:

enum FruitsEn {
    APPLE = "apple",
    BANANA = "banana",
    ORANGE = "orange",
    PEAR = "pear",
    STRAWBERRY = "strawberry"
}

interface BaseFruit {
    name: string,
    color: string
}

type FruitExtends = {
    [FruitsEn.APPLE]: {
        variety: string
    },

    [FruitsEn.BANANA]: {
        length: number
    },

    [FruitsEn.ORANGE]: {
        sections: number
    },
}

// Utility to merge two Definition B object properties shadowing A object properties
type $Merge<TA, TB> = Omit<TA, keyof TB> & TB;

// Getting the Base+Extended type of a Fruit
type Fruit<A extends FruitsEn> =
    A extends keyof FruitExtends ?
        $Merge<BaseFruit, FruitExtends[A]> :
        BaseFruit;


interface Basket {
    fruits: {
      // Interpolating the proper interface value based on the enum key
      [key in FruitsEn]: Fruit<key>
    },
    // Enum as a type is a union of their possible values
    getFruit: (fruitKey: FruitsEn, options: any) => void
}

You can further improve this pattern by easily discriminating fruits by their type:

// a type for AnyFruit
type AnyFruit = { [key in FruitsEn]: Fruit<key> }[FruitsEn];

// adding a type property on the final interfaces
type Fruit<A extends FruitsEn> =
    (A extends keyof FruitExtends ?
        $Merge<BaseFruit, FruitExtends[A]> :
        BaseFruit) & { type: A };

// you can now use it like this: 

export class NotFoundError extends Error {
    constructor (value: never) {
        super(`Value not found [${value}]`);
    }
}

function handleFruit(fruit: AnyFruit) {
    // fruit will have the following shape:
    // {
    //     type: FruitsEn,
    //     name: string,
    //     color: string,
    //     variety?: string,
    //     length?: number,
    //     sections?: number
    // }
    const type = fruit.type;
    switch (type) {
        case FruitsEn.APPLE: // handle apple here!
            break;
        case FruitsEn.BANANA: // handle banana here!
            break;
        case FruitsEn.ORANGE: // handle orange here!
            break;
        case FruitsEn.PEAR: // handle pear here!
            break;
        case FruitsEn.STRAWBERRY: // handle strawberry here!
            break;
        default: // this will *throw* at *compile* time if you forgot to handle a case
            throw new NotFoundError(type);
    }
}

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

Using ReactJS to pass an arrow function as a prop

Hey, I'm currently facing an issue where I need help creating a React component that can accept the following custom transformation: <CustomComponent transform={e=> {...e, text = e.text.toUpperCase()}}> </CustomComponent> I would real ...

Load a React component in the same spot when clicked

Is there a way to implement a functionality where clicking on different buttons in a panel will display different components in the same location? <AddNotification /> <EditNotification /> <DeleteNotification /> const AdminPanel = () =& ...

How can I extract a value from an object that is readonly, using a formatted string as the key?

I encountered a situation where I have code resembling the following snippet. It involves an object called errorMessages and multiple fields. Each field corresponds to various error messages in the errorMessages object, but using a formatted string to retr ...

Navigating the world of Typescript: mastering union types and handling diverse attributes

I am currently working on building a function that can accept two different types of input. type InputA = { name: string content: string color: string } type InputB = { name: string content: number } type Input = InputA | InputB As I try to impleme ...

Problem integrating 'fs' with Angular and Electron

Currently, I am working with Angular 6.0, Electron 2.0, TypeScript 2.9, and Node.js 9.11 to develop a desktop application using the Electron framework. My main challenge lies in accessing the Node.js native API from my TypeScript code. Despite setting "com ...

Retrieve every item in a JSON file based on a specific key and combine them into a fresh array

I have a JSON file containing contact information which I am retrieving using a service and the following function. My goal is to create a new array called 'contactList' that combines first names and last names from the contacts, adding an &apos ...

Having difficulties generating ngc and tsc AOT ES5 compatible code

I've explored various options before seeking help here. I have an angular2 library that has been AOT compiled using ngc. Currently, I am not using webpack and solely relying on plain npm scripts. Below is the tsconfig file being utilized: { "comp ...

Implementing asynchronous data sharing within an Angular 2 service

I seem to be facing a challenge that I can't quite figure out. My goal is to share data asynchronously between components that I receive from a server. Here is an example of what my service code looks like: import {Injectable} from 'angular2/co ...

When utilizing the dispatch function with UseReducer, an unexpected error is triggered: Anticipated 0 arguments were provided,

Having trouble finding a relevant answer, the only one I came across was related to Redux directly. So here's my question that might be obvious to some of you. In my code, everything appears to be correct but I'm facing an error that says: Expect ...

Bringing in the RangeValue type from Ant Design package

Currently working on updating the DatePicker component in Ant Design to use date-fns instead of Moment.js based on the provided documentation, which appears to be functioning correctly. The suggested steps include: import dateFnsGenerateConfig from ' ...

Older versions of javascript offered the assurance of a promise

Working with TypeScript and the latest ECMAScript 6 feature Promise at the moment. I'm wondering if it's possible to use Promise even if my TypeScript code is compiled into an ECMAScript 3 js-file, considering that Promise wasn't available i ...

A guide to adding a delay in the RxJS retry function

I am currently implementing the retry function with a delay in my code. My expectation is that the function will be called after a 1000ms delay, but for some reason it's not happening. Can anyone help me identify the error here? When I check the conso ...

Encountering difficulty when trying to define the onComplete function in Conf.ts. A type error is occurring, stating that '(passed: any) => void' is not compatible with type '() => void'.ts(2322)'

I have been developing a custom Protractor - browserstack framework from the ground up. While implementing the onComplete function as outlined on the official site in conf.ts - // Code snippet to update test status on BrowserStack based on test assertion ...

Typescript, left untranspiled in Karma test runs

I am attempting to conduct karma tests against Typescript. I have successfully installed karma and can run tests, but encounter Syntax Errors when my *.ts files contain Typescript syntax like this: Error: (SystemJS) SyntaxError: Unexpected token ) It s ...

Mongoose: An unexpected error has occurred

Recently, I developed an express app with a nested app called users using Typescript. The structure of my app.js file is as follows: ///<reference path='d.ts/DefinitelyTyped/node/node.d.ts' /> ///<reference path='d.ts/DefinitelyTyp ...

Mocking store.dispatch in Jest with TypeScript did not result in any function calls being made

Testing Troubles I'm a beginner in the world of testing and I'm facing some challenges. Despite going through all the documentation on jest, I couldn't find information specific to TypeScript cases. Currently, I'm on a quest to figure ...

Creating a dynamic dropdown menu where the available options in one select box change based on the selection made in another

Looking at the Material-UI Stepper code, I have a requirement to create a select element with dynamic options based on the selected value in another select within the same React component. To achieve this, I wrote a switch function: function getGradeConte ...

Issue with Parcel / React 18 App.js failing to refresh cache

Currently, I am developing a React application for my school project. However, I have encountered an issue where certain components are not rendering in my App.js file. Strangely, when I place these components as child components of App.js, they do render ...

The absence of a semicolon following the interface declaration is the issue

I am facing a slight issue with ESLint and Typescript, particularly regarding semicolons after declaring interfaces. Additionally, I utilize VSCode as my editor with automatic formatting upon saving. Below is the configuration in my .eslintrc.json file: ...

Issue at 13th instance: TypeScript encountering a problem while retrieving data within an asynchronous component

CLICK HERE FOR ERROR IMAGE export const Filter = async (): Promise<JSX.Element> => { const { data: categories } = await api.get('/categories/') return ( <div className='w-full h-full relative'> <Containe ...