Creating a function that can have either one or two arguments, with the types of the arguments determined by a specific string literal

I am looking to create a function called emitEvent(event, extra?), which will be restricted by a string literal enum of known strings such as POPUP_OPEN and POPUP_CLOSED. The function will accept a second argument that is a specifically defined dictionary shape, only to be used with certain event keys.

Below is the list of all known events in the dictionary:

interface Events {
    POPUP_OPEN: {name:'POPUP_OPEN',extra: {count:number}},
    POPUP_CLOSED: {name:'POPUP_CLOSED'},
    AD_BLOCKER_ON: {name:'AD_BLOCKER_ON', extra: {serviceName:string}},
    AD_BLOCKER_OFF: {name:'AD_BLOCKER_OFF'}
}

Example usage with type constraints:

// $ExpectType  {object: string; action: string; value: string;}
const t1 = emitEvent('POPUP_CLOSED')

// $ExpectError -> no extra argument allowed
const t11 = emitEvent('POPUP_CLOSED', {what:'bad'})

// $ExpectType  {object: string;action: string;foo: string; count: number;}  
const t2 = emitEvent('POPUP_OPEN',{count: 1231})

// $ExpectError -> extra argument is missing
const t22 = emitEvent('POPUP_OPEN')  

My approach:

One major issue with this implementation is that TypeScript does not throw an error when the 'extra' field is not defined for dictionary values.

// ✅ NO ERROR
const t2 = emitEvent('POPUP_OPEN',{count: 1231})
// ✅ Error
const t2 = emitEvent('POPUP_OPEN',{})
// 🚨NO ERROR -> THIS SHOULD ERROR !
const t2 = emitEvent('POPUP_OPEN')

Implementation Details:

type ParsedEvent<Extra = void> = Extra extends object ? BaseParsedEvents & Extra : BaseParsedEvents

type BaseParsedEvents = {
  object:string
  action:string
  value:string
}

type KnownEvents = keyof Events
type GetExtras<T> = T extends {name: infer N, extra: infer E} ? E : never;

function emitEvent<T extends KnownEvents>(event:T): ParsedEvent
function emitEvent<T extends KnownEvents, E extends GetExtras<Events[T]>>(event:T, extra: E): ParsedEvent<E>
function emitEvent<T extends string, E extends object>(event:T, extra?: E) {
  const parsedEmit = parse(event)
  return {...parsedEmit,...extra}
}


function parse(event:string): BaseParsedEvents {
    const [object,action,value] = event.split('_')  
    return {object,action,value}
}

In conclusion, while it may seem impossible to implement, I hope to find a solution :)

Answer â„–1

If you want a function to accept a variable number of arguments, consider using tuples in rest parameters instead of overloads. The `GetExtras` function will either return a tuple with a single element or an empty tuple. By spreading the result of `GetExtras` as the spread parameter of the function, you can achieve this behavior.

interface Events {
    POPUP_OPEN: {name:'POPUP_OPEN',extra: {count:number}},
    POPUP_CLOSED: {name:'POPUP_CLOSED'},
    AD_BLOCKER_ON: {name:'AD_BLOCKER_ON', extra: {serviceName:string}},
    AD_BLOCKER_OFF: {name:'AD_BLOCKER_OFF'}
}

// Example usage
const t1 = emitEvent('POPUP_CLOSED')                   // ✅ NO ERROR
const t21 = emitEvent('POPUP_OPEN',{count: 1231})      // ✅ NO ERROR
const t213 = emitEvent('POPUP_OPEN',{})                // ✅ ERROR
const t223 = emitEvent('POPUP_OPEN')                   // ✅ ERROR

type ParsedEvent<Extra extends [object] | [] = []> = Extra extends [infer E] ? BaseParsedEvents & E : BaseParsedEvents

type BaseParsedEvents = {
  object:string
  action:string
  value:string
}

type KnownEvents = keyof Events
type GetExtras<T> = T extends {name: infer N, extra: infer E} ? [E] : [];

function emitEvent<T extends KnownEvents, E extends GetExtras<Events[T]>>(event:T, ...extra: E): ParsedEvent<E>
function emitEvent<T extends string, E extends object>(event:T, extra?: E) {
  const parsedEmit = parse(event)
  return {...parsedEmit,...extra}
}


function parse(event:string): BaseParsedEvents {
    const [object,action,value] = event.split('_')  
    return {object,action,value}
}

Explore and play around with the code using this online playground.

Answer â„–2

Have you considered refining your initial function overload declaration to only apply to events that do not contain an extra property?

// specify events without the extra property
// "POPUP_CLOSED" | "AD_BLOCKER_OFF"
type KnownEventsWithoutExtra = {
  [K in keyof Events]: Events[K] extends { name: string; extra: object }
    ? never
    : K
}[keyof Events];

// limit single argument overload to above events
function emitEvent<T extends KnownEventsWithoutExtra>(event: T): ParsedEvent;

// should now produce an error
const t4 = emitEvent("POPUP_OPEN");

Playground

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

Working with arrays in Angular 4 to include new items

I am struggling with the code below: export class FormComponent implements OnInit { name: string; empoloyeeID : number; empList: Array<{name: string, empoloyeeID: number}> = []; constructor() { } ngOnInit() { } onEmpCreate(){ conso ...

Creating a component with @input for unit tests in Angular can be achieved through the following steps

I'm encountering issues while attempting to create a testing component with an @input. The component utilizes properties from the feedback model, and although I imported them into the test file, errors are being displayed. Can anyone offer assistance? ...

How can I arrange a table in Angular by the value of a specific cell?

Here's the current layout of my table: Status Draft Pending Complete I'm looking for a way to sort these rows based on their values. The code snippet I've been using only allows sorting by clicking on the status header: onCh ...

Create a .d.ts file for a custom JavaScript file

I am working on an application written in JavaScript and considering incorporating TypeScript for a new feature. Currently, I have a base class defined in JavaScript as shown below: // base.js module.exports = function BaseClass () { // ... ... }; M ...

An unusual problem encountered while working with NextJS and NextAuth

In my NextJS authentication setup, I am using a custom token provider/service as outlined in this guide. The code structure is as follows: async function refreshAccessToken(authToken: AuthToken) { try { const tokenResponse = await AuthApi.refre ...

Ways to determine the generic type of a property value from a decorated property within a decorator

While experimenting with some code, I encountered an issue where the generic type of a property value wasn't being resolved correctly when changing from TValue to (t: TValue) => TValue. Instead of being recognized as the expected number, it was now ...

Solution: How to fix the error: Invalid component type, 'Draggable' cannot be used with JSX in react-draggable

I encountered an error while working on this Next.js React project Type error: 'Draggable' cannot be used as a JSX component. Its instance type 'Draggable' is not a valid JSX element. The types returned by 'render()&apo ...

Receiving an error when triggering an onclick event for a checkbox in TypeScript

I am creating checkboxes within a table using TypeScript with the following code: generateTable(): void { var table = document.getElementById("table1") as HTMLTableElement; if (table.childElementCount == 2) { for (var x = 0; x < 2; x++) ...

Incorporating TypeScript with jQuery for efficient AJAX operations

I recently added jQuery typings to my TypeScript project. I am able to type $.ajax(...) without encountering any compile errors in VS Code. However, when I test it on localhost, I receive an error stating that "$ is not defined." In an attempt to address t ...

Mastering the proper usage of the import statement - a guide to seamless integration

I'm excited to experiment with the npm package called camera-capture, which allows me to capture videos from my webcam. As someone who is new to both npm and typescript, I'm a bit unsure about how to properly test it. Here's what I've ...

Ionic 2 faced an unresolved core-js dependency issue

Recently, I started working on a new Ionic 2 project and encountered an issue when trying to incorporate https://github.com/afrad/angular2-websocket. Upon installation, I received the warning message: UNMET PEER DEPENDENCY core-js@^2.4.1 The template pro ...

Reasons behind Angular HttpClient sorting JSON fields

Recently, I encountered a small issue with HttpClient when trying to retrieve data from my API: constructor(private http: HttpClient) {} ngOnInit(): void { this.http.get("http://localhost:8080/api/test/test?status=None").subscribe((data)=> ...

Issue with the MUI Autocomplete display in my form

Recently, I started using Material UI and Tailwind CSS for my project. However, I encountered an issue with the Autocomplete component where the input overlaps with the following DatePicker when there is multiple data in the Autocomplete. I have attached a ...

Execute the CountUp function when the element becomes visible

Currently, I am implementing the following library: https://github.com/inorganik/ngx-countUp Is there a way to activate the counting animation only when the section of numbers is reached? In other words, can the count be triggered (<h1 [countUp]="345 ...

Unnecessarily intricate: A Comparison and Enumeration of Elements in Arrays

I am facing a challenge with organizing arrays that represent categories and subjects. Each array represents a category, while each item within the array is a subject. For example: 4 Categories with Subjects ['A','B','D'] [&a ...

Comparing dates in Angular 6 can be done by using a simple

Just starting with angular 6, I have a task of comparing two date inputs and finding the greatest one. input 1 : 2018-12-29T00:00:00 input 2 : Mon Dec 31 2018 00:00:00 GMT+0530 (India Standard Time) The input 1 is retrieved from MSSQL database and the in ...

Retrieving User's Theme Preference from Local Storage in Next.js Instantly

As mentioned in various other responses, such as this one, Next.js operates on both the client and server side, requiring a guard to properly fetch from localStorage: if (typeof localStorage !== "undefined") { return localStorage.getItem("theme") } else ...

Is the variable empty outside of the subscribe block after it's been assigned?

Why is a variable assigned inside subscribe empty outside subscribe? I understand that subscribe is asynchronous, but I'm not sure how to use await to render this variable. Can someone please help me and provide an explanation? I am attempting to retr ...

Avoid unwanted typeof warnings in TypeScript and WebStorm combination

How can I handle unwanted TypeScript checks related to JavaScript usage in today's development environment? Consider the following function: function connect(config: string): void { // Getting warning for the line that follows: // typeof ...

Display the ion-button if the *ngIf condition is not present

I am working with an array of cards that contain download buttons. My goal is to hide the download button in the first div if I have already downloaded and stored the data in the database, and then display the second div. <ion-card *ngFor="let data o ...