Unique Typescript Generics

I'm exploring a TypeScript feature that I need for a specific requirement. I want to create a structure to represent a collection of events and event handler tuples. Below is my current progress.

interface Event {
 type: string;
}

type EventType<T extends Event> = T['type'];
type EventHandler<T extends Event> = (event:T): void;

// making some progress here
type EventTypeAndHandlerTuple <T extends Event> = [EventType<T>, EventHandler<T>];


// struggling with this part, currently using 'any'
type EventTypeAndHandlerTuples = EventTypeAndHandlerTuple<any>[]

I hope to define code like this

const todoAddedEvent: TodoAddedEvent = {...}
const handleTodoAddedEvent: TodoAddedHandler = (event: TodoAddedEvent) => {...};
const todoRemovedEvent: TodoRemovedEvent = {...}
const handleTodoRemovedEvent: TodoRemovedHandler = (event: TodoRemovedEvent) => {...}

// NEED HELP ASSIGNING A TYPE TO THIS 😢
const eventTypeToHandlerTuples = [
 [todoAddedEvent, handleTodoAddedEvent],
 [todoRemovedEvent, handleTodoRemovedEvent]
];

I am looking for a type for eventTypeToHandlerTuples that ensures it contains correctly associated event types and event handler tuples. It should fail if there is an incorrect pairing. For instance, this should fail

// this should fail because the removed event is being paired with the add event handler.
const eventTypeToHandlerTuples = [
 [todoRemovedEvent, handleTodoAddedEvent], 
];

Thank you in advance for your help!

Answer â„–1

To easily accomplish this task, consider aggregating a union of all relevant Event types with unique string literal types for their type properties. This union can be utilized to differentiate the union. Define the union as follows:

type Events = TodoAddedEvent | TodoRemovedEvent;

Next, describe the union of all possible

EventTypeAndHandlerTuple<T>
types for each element T in the Events union using techniques like distributive conditional types:

type UnionOfEventTypeAndHandlerTuples = Events extends infer T ?
  T extends Event ? EventTypeAndHandlerTuple<T> : never : never;
// type UnionOfEventTypeAndHandlerTuples = 
// EventTypeAndHandlerTuple<TodoAddedEvent> | EventTypeAndHandlerTuple<TodoRemovedEvent>

The desired type is now simply

UnionOfEventTypeAndHandlerTuples[]
:

// valid
const eventTypeToHandlerTuples: UnionOfEventTypeAndHandlerTuples[] = [
  [todoAddedEvent, handleTodoAddedEvent],
  [todoRemovedEvent, handleTodoRemovedEvent]
];

// error
const badEventTypeToHandlerTuples: UnionOfEventTypeAndHandlerTuples[] = [
  [todoRemovedEvent, handleTodoAddedEvent], // error!
];

If you don't have a pre-defined union, the process becomes more challenging. In the absence of native support for existential generic types in TypeScript (see microsoft/TypeScript#14466), it's not feasible to have a specific type like

EventTypeAndHandlerTuple<exists T extends Event>[]
, where exists T means "I'm indifferent about the exact nature of T, I just need it to exist". You might consider existential types as an "infinite union" of compatible types.

In such scenarios, employing regular generic types and utilizing helper functions could be a viable approach in TypeScript:

const asEventTypeToHandlerTuples = <T extends Event[]>(
  tuples: [...{ [I in keyof T]: EventTypeAndHandlerTuple<Extract<T[I], Event>> }]
) => tuples;

In this setup, the asEventTypeToHandlerTuples() function receives an argument named

tuples</code, constraining its type to a <a href="https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-1.html#mapped-types-on-tuples-and-arrays" rel="nofollow noreferrer">mapped tuple type</a>. It transforms an array of <code>Event
types into the corresponding array of EventTypeAndHandlerTuple types based on the inferred type of
T</code. By allowing the compiler to <em>infer</em> types rather than explicitly declaring them, the codebase remains cleaner:</p>
<pre><code>// valid
const eventTypeToHandlerTuples = asEventTypeToHandlerTuples([
  [todoAddedEvent, handleTodoAddedEvent],
  [todoRemovedEvent, handleTodoRemovedEvent]
]);

// error
const badEventTypeToHandlerTuples = asEventTypeToHandlerTuples([
  [todoRemovedEvent, handleTodoAddedEvent], // error!
]);

While dealing with unions of particular types like Events tends to be simpler than handling generic type parameters throughout your codebase, generic helper functions serve as effective alternatives when existential types are unattainable.

Click here for Playground link to code

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

Enforce TypeScript's compliance with class types

Here is a code snippet that I came across: class A { public var1: string = 'var1'; } class B { public var1: string = 'var1'; public var2: string = 'var2'; } const instance: A = new B(); console.log(instance insta ...

Utilize JavaScript to assign a unique color to each category

I'm currently working on a JavaScript task My goal is to assign specific colors to different categories For example: if category name = x then color = blue if category name = y then color = red ... I attempted the following code, but it seems like ...

Using Console.log() will display 'undefined' prior to receiving any data

I am facing a problem with a lifecycle hook that I have been trying to troubleshoot. export default class EditRevision extends Component { state = { data: [], customColumns: [] } componentWillMount = () => { axios.get('http:/ ...

"Efficiently fetch data with an Express GET request without the

My goal is to send the client the HTML page (create.html) in response to a GET request triggered by a button click using fetch. I am intentionally avoiding the use of a form due to formatting and potential scalability issues. The code acknowledges that the ...

Executing Actions Prior to Redirecting

I am working on implementing a timer process using a custom JQuery plugin that will redirect to the login page after a specific number of minutes. Additionally, when the timer reaches zero, I need to execute a task, which in this case involves making a cal ...

Creating two separate divs that can scroll independently while also limiting each other's scroll depth can be achieved by utilizing

I am attempting to replicate the unique scrolling feature seen on this particular page. Essentially, there are two columns above the fold that can be scrolled independently, but I want their scroll depths to be linked. When a certain depth is reached whil ...

Set up a Bootstrap date picker to populate two input boxes with a start and end date. Additionally, disable the ability for the date value to change

In my project, I have implemented Bootstrap Datepicker to set two input boxes for selecting start and end dates. The rule is that the start date must be after today and the end date must be after the selected start date. To disable specific dates in the da ...

Ensuring a consistent height for a Angular JS Auto Complete feature

The ng2-auto-complete results appear to be lengthy. When I try to inspect and apply some CSS, the results disappear upon clicking. To address this issue, I experimented with the following code: ng2-auto-complete{ height: 400px; overflow-y: scroll ...

Stop a loop that includes an asynchronous AJAX function

Embarking on my Javascript journey as a beginner, I find myself facing the first of many questions ahead! Here is the task at hand: I have a directory filled with several .txt files named art1.txt, art2.txt, and so on (the total count may vary, which is ...

What could be the reason behind Angular choosing to send an HTTP request to `https://localhost:4200` instead of `http://localhost:5001` in order to retrieve data

Using Angular version 15.0 has been smooth sailing on the backend, but when making service requests on the frontend, an error is encountered: Failed to load resource: the server responded with a status of 404 (Not Found) This is due to the request URL: ht ...

Iterating through two variables and running tests every 1000 milliseconds

I am working with two variables: const frame = $('#avacweb_chat iframe'); let time = $('.date-and-time' , frame.contents()).last().text(); let newTime = setInterval(function() { let newT = $('.date-and-time' , frame.content ...

Preventing multiple clicks on a link

Is there a method in vanilla JavaScript (not jQuery) to prevent a link from triggering an event multiple times? I am looping through some anchor elements and adding an onclick event to each link, which displays certain content from a json file. The issue ...

Tips for extracting a value from a currently active list item's anchor tag with JQuery on Mapbox API?

Currently, I am attempting to extract the value from a forward geocoder that predicts addresses while a user is typing. My goal is to then send this value to a form with an id of "pickup". However, I am encountering difficulties in capturing the li > a ...

Utilizing Vue to create multiple components and interact with Vuex state properties

I am currently learning Vue and using it with Vuex (without Webpack). However, I am facing some challenges while implementing a simple example and the documentation is not very clear to me. One issue I encountered is that I cannot access the Vuex st ...

What is the best way to implement CSS Float in typescript programming?

For a specific purpose, I am passing CSS Float as props. To achieve this, I have to define it in the following way: type Props = { float: ???? } const Component = ({ float }: Props) => {......} What is the most effective approach to accomplish this? ...

Having difficulty employing jest.mock with a TypeScript class

Following the guidelines outlined in the ES6 Class Mocks page of the Jest documentation, I attempted to test a method on a TypeScript class called Consumer. The Consumer class instantiates a Provider object and invokes methods on it, prompting me to mock t ...

How can I utilize a callback in TypeScript when working with interfaces?

Can someone guide me on how to implement an interface in typescript with callback function? interface LoginCallback{ Error: boolean, UserInfo: { Id: string, OrganizationId: string } } interface IntegrationInterface { Ini ...

Click event in safari failing to trigger on iPhone

Issue with click event on mobile Safari browser when using an iPhone In a React class component, the click event listener is triggered on the window when opening the menu. This functionality works correctly everywhere except for iPhone Safari. toggleMenu ...

Using Node.js, iterate over every key-value pair within a Redis hash data structure

I am currently navigating my way through the world of redis and node, attempting to iterate over some test hash keys that I have generated. My goal is to display the results on the screen. The expected output should look like this: { "aaData": [['Tim ...

Retrieve the JSON data by passing the variable name as a string

There are JSON files embedded in the page, like so: <script type="text/javascript" language="javascript" src="json/divaniModerni.json"></script> <script type="text/javascript" language="javascript" src="json/divaniClassici.json"></scr ...