Unexpectedly, the second argument of Typescript `Parameters<Fnc>` is returning `never`

I have a function called trackEvent that is correctly typed, but when using the Parameters utility, the type does not carry over in the same way.

Is there a method of utilizing generics to properly handle these arguments in an array format? (as shown in the example alternativeTrackEvent)

The concept would be to pass it through a React component like this:

<Button eventParams={['car', 'drive', { city: 'london'}]} />
export type Events = {
  car: {
    drive: { city: string };
  },
  plane: {
    fly: { country: string };
  }
};

export type TrackEvent = <K extends keyof Events, E extends keyof Events[K], P extends Events[K][E]>(
  tag: K,
  name: E,
  opts: P,
) => void;

const trackEvent: TrackEvent = (tag, name, opts) => console.log(tag, name, opts)

trackEvent('car', 'drive', { city: 'london'}) // works

trackEvent('plane', 'drive', { country: 'uk'}) // fails correctly

const alternativeTrackEvent: Parameters<TrackEvent> = ['car', 'drive', { city: 'london'}] // second argument is never

Playground link

Answer №1

Higher order generics in TypeScript do not fully support representing Parameters<TrackEvent> without losing information. If TypeScript included existentially quantified generic types as requested in microsoft/TypeScript#14466, then Parameters<TrackEvent> could potentially be defined differently.

type TrackEventParams = Parameters<TrackEvent>;
// This syntax is invalid and should not be used
/* type TrackEventParams =
      <∃K extends keyof Events, ∃E extends keyof Events[K], ∃P extends Events[K][E]>[
        tag: keyof Events, name: never, opts: never
      ]
*/

Existential generic types are currently not supported in TypeScript, so the utility of Parameters<T> utilizes conditional types with constraints when matching against a generic function.

By specifying constraints like K extends keyof Events, E extends keyof Events[K], P extends Events[K][E], Parameters<TrackEvent> behaves as if TrackEvent were initially defined like:

type K = keyof Events;
type E = keyof Events[K];
type P = Events[K][E];
type TrackEvent = (tag: K, name: E, opts: P) => void;
// type TrackEvent = (tag: keyof Events, name: never, opts: never) => void

The reason why E is 'never' in this case is due to the lack of common property names between Events["car"] and Events["plane"], leading to only key names shared among all members of the union being returned by keyof.


In cases where existentially quantified generics are necessary but no specific type equivalent exists in TypeScript, existential types can be thought of as the union of the type for all possible instantiations of the generic. Finite unions, which exist in TypeScript, can accurately represent constrained types like K extends keyof Events.

To create a finite union programmatically using distributive object types, you can define TrackEventParams as demonstrated below:

type TrackEventParams = { [K in keyof Events]:
  { [E in keyof Events[K]]:
    [tag: K, name: E, opts: Events[K][E]]
  }[keyof Events[K]]
}[keyof Events];

/* type TrackEventParams =
    [tag: "car", name: "drive", opts: { city: string; }] | 
    [tag: "plane", name: "fly", opts: { country: string; }] */

This specific type can be used instead of Parameters<TrackEvent>. Additionally, TrackEvent can be redefined in terms of TrackEventParams depending on the use case.

For more details and examples, see the provided 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

Clicking the button on an Ionic/Angular ion-item will toggle the visibility of that item, allowing

I'm currently working with Ionic 5 / Angular and I have a collection of ion-item's, each containing a button. Take a look at the code snippet below: <ion-list> <ion-item> <ion-label>One</ion-label> ...

Utilizing Typescript generics with an optional second parameter

Learning about generics in typescript has been quite challenging for me. However, I was able to make it work successfully. export type Events = { LOGIN: undefined NAVIGATION: { screen: string } SUPPORT: { communication_method: 'chat&ap ...

What is the best way to dynamically access or create nested objects by iterating through a loop based on a given number?

Can someone assist me in refactoring a couple of if-statements that are almost identical, but differ only in hierarchy within the currentHierarchie object? I am looking to consolidate these into a loop or similar construct. The number of if statements shou ...

Guide on effectively testing the catch block of a Typescript function using mocha and chai

In my TypeScript class, I have implemented several functions, each containing a try-catch block that returns a predetermined response upon encountering an error. While writing unit tests using Mocha and Chai, I am facing difficulties in intentionally trig ...

Refreshing Custom Functions within Excel Add-On - Web Edition

Currently, I am working on an Excel Add-In that includes custom functions utilizing the Javascript API. I have been following a particular tutorial for guidance. While attempting to debug using the Web version of Excel due to its superior logging capabili ...

The process of prioritizing specific elements at the top of an array when ordering

In my array, I want to prioritize specific items to always appear at the top. The API response looks like this: const itemInventorylocationTypes = [ { itemInventorylocationId: '00d3898b-c6f8-43eb-9470-70a11cecbbd7', itemInvent ...

The Firebase EmailPasswordAuthProvider is not a valid type on the Auth object

When working in an Angular2/TypeScript environment, I encountered an error when trying to use the code provided in the Firebase documentation. The error message displayed was "EmailPasswordAuthProvider Does Not Exist on Type Auth". var credential = fireba ...

Guide on importing a module dynamically in a NodeJS script with custom path aliases set in tsconfig.json

Our project utilizes React and Typescript along with Nx, WebPack, and Babel. Within our monorepo, we have a multitude of applications. To enhance our i18n efforts, I am currently developing a git hook that will scan each application directory and generate ...

Is there a method to automatically select or deselect a checkbox based on the incoming value in Angular?

When new data comes in, the table gets populated and I can't specify that "A" should be checked while "D" shouldn't. re(ref: getrefactormodel, count:number){ let data= this.fb.group({ word_to_rename: [ref.word_to_rename, Vali ...

I am encountering an error where authManager is not defined

I am encountering difficulties with authentication through Auth0. An error message stating "authenticate is undefined on the authManager" keeps appearing. The authManager is supposed to be injected into the authService, and both are added in the app unde ...

Determining the argument type in Typescript based on the value of a previous argument

I am struggling to implement type checking on the payload argument of the notify method in the following class. Here is the code I have tried: type UserNotificationTypes = { ASSIGNED_TO_USER: { assignedUserId: string } MAIL_MESSAGE_SENT: { re ...

What is the reason for calling Proxy on nested elements?

Trying to organize Cypress methods into a helper object using getters. The idea is to use it like this: todoApp.todoPage.todoApp.main.rows.row .first().should('have.text', 'Pay electric bill'); todoApp.todoPage.todoApp.main.rows.ro ...

A step-by-step guide to building a basic node.js TypeScript project using ES6 modules and starting with a simple "Hello World

Consider a scenario where you have a basic program consisting of a main file and some functions stored in a separate file, utilizing ES6 modules and designed to be executed with node.js. my-helpers.ts: import * as fs from "fs"; export function readBasic ...

Show the outcome stored within the const statement in TypeScript

I am trying to display the outcome of this.contract.mint(amount, {value: this.state.tokenPrice.mul(amount)}) after awaiting it. I want to see the result. async mintTokens(amount: number): Promise<void> { try { let showRes = await this.c ...

Creating a generic union type component in Typescript/Angular 10

My interfaces are as follows: export interface Channel { id: number; name: string; } export interface TvChannel extends Channel { subscribed: boolean; } export interface RadioChannel extends Channel { // marker interface to distinguish radio chan ...

Facing compilation issues when using Typescript with Svelte

My project was set up using npm create svelte@latest, with ts enabled and tailwind added. However, when declaring <script lang="ts">, I encountered an Unexpected Token error, like the one below: D:/gits/rpg-board/src/lib/components/FilterB ...

Encountering a TypeScript error in Vue 3 when trying to assign a type of '($event: any) => void' to an event listener

Snippet of component code: <h2 @click="handleEvent(post.id)">{{ post.title }}</h2> function handleEvent(id: number) { router.push("/post/" + id); } Error message in TypeScript: Type '($event: any) => void' i ...

Utilizing Angular 2 Component Input Binding with the Expression Mapping Function

Currently, I am working on a component that has an input of type array. class FooComponent { @Input() selectedItemIds:String[]; } I want to utilize a map expression in binding within the parent component. <app-foo-component [selectedItemIds]=&apo ...

Acquiring the class function from a service which yields a model

Within my class called userName, I have defined properties that form a model when casting from json. Additionally, this class includes a simple function that returns the full Name: export class userName { firstName: string; lastName: string; g ...

Data not reflecting changes in component when field is modified?

I currently have a table on my web page that displays data fetched from an array of objects. The data is collected dynamically through websockets by listening to three different events. User can add an entry - ✅ works perfectly User can modify ...