Creating a signature for a function that can accept multiple parameter types in TypeScript

I am facing a dilemma with the following code snippet:

const func1 = (state: Interface1){
    //some code
}

const func2 = (state: Interface2){
    //some other code
}

const func3: (state: Interface1|Interface2){
//some other code
}

However,

func3(func1)

results in an error being thrown.

My intention was to create a function called func3 that is capable of accepting either func1 or func2 as a parameter. How can I achieve this in TypeScript?

Answer №1

func3 requires passing either Interface1 or Interface2 when calling func1.

The type signature of func1 is:

(state: Interface1) => void

If you intend to pass a callback, the argument type of func3 needs adjustment:


type Interface1 = {
    tag: 'Interface1'
}

type Interface2 = {
    tag: 'Interface2'
}
const func1 = (state: Interface1) => {
    //some code
}

const func2 = (state: Interface2) => {
    //some other code
}

const func3 = (cb: typeof func1 | typeof func2) => {
    //some other code
}

func3(func1) // okay

If you're interested in function composition, check out this article.

UPDATE:

Previous solutions work; however, calling callback is impossible. My mistake, apologies for that.

To enable this functionality, some code refactoring is required as union types alone may not suffice with functions.

Consider the following example:


type Union = typeof func1 | typeof func2

const func3 = (cb: Union) => {
    cb({ tag: 'Interface2' }) // error
}

func3(func1) // okay

In the above scenario, cb is inferred as (arg:never)=>any. Why?

Please refer to @jcalz's excellent answer on intersections.

The crux here is - types in contravariant positions get intersected.

Due to the impossibility of creating Interface1 & Interface2, it resolves to never.

For more insights on contravariance, variance, etc., please visit my question.

Hence, TS struggles to differentiate allowed vs. disallowed arguments.

As witnessed in the example, although func1 was used as an argument, attempting to call the callback with Interface2 could lead to runtime errors.

A workaround can be implemented as follows:

type Interface1 = {
    tag: 'Interface1'
}

type Interface2 = {
    tag: 'Interface2'
}

const func1 = (state: Interface1) => {
    //some code
}

const func2 = (state: Interface2) => {
    //some other code
}

type Fn = (...args: any[]) => any

function func3<Cb extends Fn, Param extends Parameters<Fn>>(cb: Cb, ...args: Param) {
    cb(args)
}

const x = func3(func1, { tag: 'Interface1' }) // okay

Function arguments are contravariant to each other if attempting to create a union of functions leading to errors.

UPDATE 3:

If the desired outcome differs from expectations, utilizing a typeguard within func3 to compute func1 argument is necessary:


type Interface1 = {
    tag: 'Interface1'
}

type Interface2 = {
    tag: 'Interface2'
}
const func1 = (state: Interface1) => {
    //some code
}

const func2 = (state: Interface2) => {
    //some other code
}

type Fn = (a: any) => any

// typeguard
const isFunc = <R extends typeof func1 | typeof func2>(cb: Fn, cb2: R): cb is R => cb === cb2

const func3 = (cb: typeof func1 | typeof func2) => {
    if (isFunc(cb, func1)) {
        cb({ tag: 'Interface1' })
    } else {
        cb({ tag: 'Interface2' })
    }
}

func3(func1) // okay

Answer №2

In typescript, it's important to note that the language is very strict. This means when using func3, you must be specific about the parameter being passed in. Here's an example of how you can achieve this:

type Interface1={
//insert code here
}
type Interface2={
//insert code here
}
type Interface3 = ((state:Interface1)=>void) | ((state:Interface2)=>void)

const func1 = (state: Interface1)=>{
//insert code here
}

const func2 = (state: Interface2)=>{
//insert code here
}

const func3= (state: Interface3)=>{
//insert code here
}
//both calls to func3 below are valid
func3(func1);
func3(func2);

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

The attribute 'forEach' is not recognized on the data type 'string | string[]'

I'm experiencing an issue with the following code snippet: @Where( ['owner', 'Manager', 'Email', 'productEmail', 'Region'], (keys: string[], values: unknown) => { const query = {}; ...

Testing a function within a class using closure in Javascript with Jest

Currently, I am attempting to simulate a single function within a class that is declared inside a closure. const CacheHandler = (function() { class _CacheManager { constructor() { return this; } public async readAsPromise(topic, filte ...

Attempting to create a login feature using phpMyAdmin in Ionic framework

Currently, I am in the process of developing a login feature for my mobile application using Ionic. I am facing some difficulties with sending data from Ionic to PHP and I can't seem to figure out what the issue is. This is how the HTML form looks li ...

Tips for extracting a keyword or parameters from a URL

I'm in the process of creating my personal website and I am interested in extracting keywords or parameters from the URL. As an illustration, if I were to search for "Nike" on my website, the URL would transform into http://localhost:3000/searched/Nik ...

incorrect indexing in ordered list

I am facing an issue with the ngIf directive in Angular. My objective is to create a notification system that alerts users about any missing fields. Here's a stackblitz example showcasing the problem: https://stackblitz.com/edit/angular-behnqj To re ...

Sorting through a JavaScript array

I am facing a peculiar problem while trying to filter an array in TypeScript. Here is the structure of my object: Sigma.model.ts export class Sigma { sigmaId: number; name: string; userId: number; starId: string; } `` The starId property contains com ...

Instructions for disabling editing for a specific cell within an inline editable row in primeNG

I am currently using PrimeNG DataTable with Angular, where the rows are editable as shown in the example in the documentation: https://www.primefaces.org/primeng/#/table/edit. However, I am facing an issue where I want to exclude one cell from being editab ...

Issues with Angular 4 Rxjs subject subscription functionality

My application consists of a shared service named data.service.ts, which contains the following code: public pauseProjectTask$: Subject<any> = new Subject<any>(); pauseTaskProject(taskData, type){ this.pauseProjectTask$.next(taskData); ...

There seems to be an issue in Angular as it is unable to retrieve /

I'm encountering an issue with my simple application where I am receiving the error message "Cannot GET /." Also, in the console, I see this error: TypeError: Cannot read property 'checked' of null at Object.SL_BBL_locer. I'm unsure ab ...

Executing npm prepublish on a complete project

I am facing a situation with some unconventional "local" npm modules that are written in TypeScript and have interdependencies like this: A -> B, C B -> C C -> D When I run npm install, it is crucial for all TypeScript compilation to happen in t ...

Angular2 authguards encountering issues when trying to run asynchronous functions

I need a way to safeguard my routes by verifying if a user is logged in from the server, but I'm facing issues with asynchronous functions not executing properly. Below is the code snippet that's causing trouble: canActivate (route: ActivatedRo ...

My component fails to load using Angular Router even though the URL is correct

I have been experiencing an issue while trying to load my Angular component using the router. The component never appears on the screen and there are no error messages displayed. app-routing-module { path: '', redirectTo: '/home', ...

Using TypeScript's type casting functionality, you can easily map an enum list from C#

This is a C# enum list class that I have created: namespace MyProject.MyName { public enum MyNameList { [Description("NameOne")] NameOne, [Description("NameTwo")] NameTwo, [Description("NameThree")] NameThree ...

Encountering an issue in a Next.js application while building it, where an error is triggered because the property 'protocol' of 'window.location' cannot be destructured due to being undefined

While building my nextjs application, I encountered the following error. My setup uses typescript for building purposes, although I am only using JavaScript. Build error occurred: TypeError: Cannot destructure property 'protocol' of 'window ...

Definition of Stencil Component Method

I'm encountering an issue while developing a stencil.js web component. The error I'm facing is: (index):28 Uncaught TypeError: comp.hideDataPanel is not a function at HTMLDocument. ((index):28) My goal is to integrate my stencil component i ...

Shifting Angular Component Declarations to a New Location

Here's a question that might sound silly: In my Angular project, I am looking to reorganize my component declarations by moving them from angular.module.ts to modules/modules.modules.ts. The goal is to structure my src/app directory as follows: src ...

Array of dynamically typed objects in Typescript

Hello, I am a newbie to Typescript and I recently encountered an issue that has left me stumped. The problem I am facing involves feeding data to a Dygraph chart which requires data in the format [Date, number, number,...]. However, the API I am using prov ...

The type '{ children: ReactNode; }' does not share any properties with the type 'IntrinsicAtrributes'

I have explored several discussions on the topic but none of them have provided a solution to my issue. My objective is to develop a reusable Typography component that resembles the following structure: import React from 'react' import type { Ty ...

Analysis of cumulative revenue using Palantir Foundry Function

I am in need of creating a function that can transform raw data into target data. The raw data consists of a table with columns for customer, product, and revenue, while the target data includes customer, revenue, and cumulative revenue. customer produ ...

Utilizing numerical values in useParams - A beginner's guide

Trying to access specific data from my json file using an ID, like "http://localhost:3001/pokemons/3", leads to a 404 error. All the data is visible at http://localhost:3001/pokemons. It seems that useParams doesn't want me to use id as a number - q ...