What is the best approach to converting an array of strings into a TypeScript type map with keys corresponding to specific types?

The code provided below is currently working without any type errors:

type Events = { SOME_EVENT: number; OTHER_EVENT: string }

interface EventEmitter<EventTypes> {
  on<K extends keyof EventTypes>(s: K, listener: (v: EventTypes[K]) => void);
}

declare const emitter: EventEmitter<Events>;

emitter.on('SOME_EVENT', (payload) => testNumber(payload));
emitter.on('OTHER_EVENT', (payload) => testString(payload));

function testNumber( value: number ) {}
function testString( value: string ) {}

(example on TS Playground)

However, I am interested in utilizing something similar to an enum for autocompletion of the type names. This way, I can write the following instead of using string literals:

emitter.on(EventNames.SOME_EVENT, (payload) => testNumber(payload));
emitter.on(EventNames.OTHER_EVENT, (payload) => testString(payload));

I aim to maintain DRY principles, so I am exploring options to achieve this without duplicating all event names in a new type.

In JavaScript, specifically, I can easily implement the following:

const EventNames = [
    'SOME_EVENT',
    'OTHER_EVENT',
]

const Events = {}

for (const eventName of Events) {
    Events[eventName] = eventName
}

// and then use it:

emitter.on(Events.SOME_EVENT, (payload) => testNumber(payload));
emitter.on(Events.OTHER_EVENT, (payload) => testString(payload));

Hence, in plain JS, I can generate enum-like objects without needing to resort to less DRY alternatives like:

const Events = {
    SOME_EVENT: 'SOME_EVENT', // duplicates the names
    OTHER_EVENT: 'OTHER_EVENT',
}

emitter.on(Events.SOME_EVENT, (payload) => testNumber(payload));
emitter.on(Events.OTHER_EVENT, (payload) => testString(payload));

My goal is to maximize DRYness in TypeScript by defining each event name only once while also associating a type with the event payloads.

One approach that is less DRY involves repeating the event names three times:

type Events = { SOME_EVENT: number; OTHER_EVENT: string }

const EventNames: {[k in keyof Events]: k} = {
  SOME_EVENT: 'SOME_EVENT', OTHER_EVENT: 'OTHER_EVENT'
}

interface EventEmitter<EventTypes> {
  on<K extends keyof EventTypes>(s: K, listener: (v: EventTypes[K]) => void);
}

declare const emitter: EventEmitter<Events>;

emitter.on(EventNames.SOME_EVENT, (payload) => testNumber(payload));
emitter.on(EventNames.OTHER_EVENT, (payload) => testString(payload));

function testNumber( value: number ) {}
function testString( value: string ) {}

(Playground link)

Therefore, my query is: Is there a method to structure this in a way where I define each event only once along with their payload types?

If achieving this level of optimization with just one instance of each event name is not viable, what would be the best alternative approach?

Answer №1

I have discovered a method to maintain DRYness by defining each event name only once. This involves utilizing a dummy class that not only stores type information but also generates JavaScript output for easy iteration, allowing us to create an enum-like structure with proper typing:

class EventTypes {
    constructor(
        // Map event names to respective payload types here.
        public SOME_EVENT: number,
        public OTHER_EVENT: string
    ) {}
}

const EventNames = {} as { [k in keyof EventTypes]: k }

for (const key in new (EventTypes as any)) {
    EventNames[key] = key
}

console.log(EventNames)

interface EventEmitter<EventTypes> {
  on<K extends keyof EventTypes>(s: K, listener: (v: EventTypes[K]) => void);
}

declare const emitter: EventEmitter<EventTypes>;

emitter.on(EventNames.SOME_EVENT, (payload) => testNumber(payload));
emitter.on(EventNames.OTHER_EVENT, (payload) => testString(payload));

function testNumber( value: number ) { console.assert(typeof value === 'number') }
function testString( value: string ) { console.assert(typeof value === 'string') }

playground link

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

An effective method to utilize .map and .reduce for object manipulation resulting in a newly modified map

Here's an example of what the object looks like: informations = { addresses: { 0: {phone: 0}, 1: {phone: 1}, 2: {phone: 2}, 3: {phone: 3}, 4: {phone: 4}, 5: {phone: 5}, }, names: { 0 ...

Utilize TypeScript function types in React for enhanced functionality

I have made the decision to refactor a project that was originally created with vanilla JavaScript and now I want to transition it to TypeScript. One issue I am facing is how to pass a function as a type on an interface. Although I referred to the TypeScr ...

Vuetify 3 does not display dialogs

I am attempting to integrate vuetify 3.alpha with vue 3. Below are the files I am working with: Temp.vue (obtained from vuetify example) <template> <div class="text-center"> <v-dialog v-model="dialog" w ...

Configuring the parameters property for a SSM Association in AWS CDK

I am working on utilizing the AWS Systems Manager State Manager to automate shutting down an RDS instance at 9PM using a cron job. Currently, I am constructing the CloudFormation template with the help of AWS CDK. While going through the AWS CDK documenta ...

Tips for fixing the TS2345 compilation error when working with React

Attempting to implement the setState method in React has resulted in a compile error. Any solutions to this issue would be greatly appreciated. Frontend: react/typescript articleApi.tsx import axios from 'axios'; import {Article} from '../ ...

Executing the plugin-typescript library within an Angular 2 project hosted on localhost

I am encountering an issue every time I run my angular2 project where numerous files are being loaded, including the 'ts' library from unpkg.com/plugin-typescript/lib/plugin.js. I am looking to eliminate the need for this file to load from anothe ...

Locate every instance where two arrays are compared in TypeScript

My goal is to search for matches in Object 2 where the _type corresponds to filterByCallTypeTitulo in Object 1, and then create a new array including all the matched information from Object 2. I attempted to achieve this using the filter() method and forE ...

How is it possible that there is no type error when utilizing copy with spread syntax?

When I use the map function to make a copy of an array of objects, why doesn't it throw an error when adding a new property "xxx"? This new property "xxx" is not declared in the interface. interface A{ a:number; b:string; }; let originalArray:A[] ...

Route protection is ineffective when dealing with two observables simultaneously

After writing the route guard as shown below, I encountered an issue with the else statement that was not returning a result, even though it should have. Surprisingly, there were no errors either. this.hotelSettingsService.get().pipe(map(res => { ...

Dealing with challenges in integrating ngx-masonry with Angular 14

I am currently working with Angular 14 framework and the ngx-masonry library (https://www.npmjs.com/package/ngx-masonry/v/14.0.1). However, I am facing some issues where it is not functioning correctly. I would appreciate any assistance or guidance on how ...

Mocking a common function in a shared service using Typescript and Jest

I have a service that is utilized with NestJS, although the issue at hand is not specific to NestJS. Nonetheless, testing in NestJS is involved, and I use it to create the service for testing purposes. This service is responsible for making multiple calls ...

Developing collaborative functions in Angular

Is there a way in Angular 9 to directly call static methods from HTML without using shared services or defining methods in components? I came across an old approach on How to call static method of other class in .html (not in .ts)?, but I am curious if the ...

Is it possible to set up tsc to compile test specifications specifically from a designated directory?

I have been working on integrating e2e tests into an Angular project that was not originally set up with @angular-cli, so I have been manually configuring most of it. Currently, I am trying to define a script in the package.json file to transpile only the ...

What is the process for obtaining the result of an asynchronous function triggered by an event in HTML?

I am currently working on an Angular application where I have a button that triggers a click event. The method being called is asynchronous and involves subscribing to an observable. My goal is to call player.start() only after the listItems.getData() meth ...

What is TypeScript's approach to managing `data-*` attributes within JSX?

Let's consider the following code snippet: import type { JSX } from 'react'; const MyComponent = (): JSX.Element => ( <div data-attr="bar">Foo</div> ); Surprisingly, this code does not result in any TypeScript er ...

Issue with Angular ngFor within a particular dialog window?

(respPIN and internalNotes are both of type InternalNotes[]) When the code in encounter.component.ts is set like this: this.ps.GetInternalNotes(resp.PersonID.toString()).subscribe(respPIN => { this.internalNotes = respPIN; }); An ERROR occurs: Err ...

Inspect the TypeScript typings within Svelte documents directly from the terminal

When I run tsc --noemit, it successfully checks for type errors in the codebase. However, I have encountered an issue where it does not seem to check .svelte files. Is there a way to enable this functionality? I can see the type errors in .svelte files wh ...

Is it possible to retrieve a value obtained through Request.Form?

Within my Frontend, I am passing an Object with a PersonId and a FormData object. const formData = new FormData(); for (let file of files){ formData.append(file.name, file,); } formData.append('currentId',this.UserId.toString()); const upl ...

What is the equivalent of Typescript's Uint8Array and Uint16Array in Python?

new Uint8Array(new Uint16Array([64]).buffer) How can I achieve a similar data structure in pure Python? What is the equivalent of Uint8Array/Uint16Array? I am extracting a buffer from a Uint16Array type here and converting it to a Uint8Array, but I am un ...

The array used within the useEffect hook and the getCoordinates function appears to be distinct when printed with console

Utilizing GoogleMap API for Custom Location Display I have an imported array of JSON objects named data which includes an address property. The Google Maps API is used to retrieve coordinates from the addresses in order to generate custom markers displaye ...