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

React typescript: When defining an interface in RouterProps, it is important to remember that it can only extend an object type or a combination of object types

For my React project, I decided to implement Typescript. After seeking assistance from Chatgpt, I was able to obtain this code snippet: import React from "react"; import { Route, Navigate, RouteProps } from "react-router-dom"; import { ...

Updating the value of a key in an object is not functioning as expected

There is a single object defined as requestObject: any = { "type": 'type1', "start": 0, "size": 10, "keywords": ['abcd','efgh'], filters: [], } Next, attempting to change the value for keyword, I updat ...

I am puzzled as to why I am still facing the error message: "'node' is not recognized as an internal or external command, operable program or batch file."

I'm in the process of setting up typescript for a new node project. Here are the steps I've taken so far: Installing typescript: npm i --save-dev typescript Installing ts-node: npm i --save-dev ts-node Installing the types definition for node ...

What is the reasoning behind ethers.js choosing to have the return value of a function be an array that contains the value, rather than just the value itself

An issue arose with the test case below: it('should access MAX_COUNT', async () => { const maxCount = await myContract.functions.MAX_COUNT(); expect(maxCount).to.equal(64); }); The test failed with this error message: ...

Utilize cypress to analyze the loading time of a webpage

My current challenge involves using cypress to carry out website tests. I am looking for a reliable method to measure the duration it takes for certain cypress commands to load or execute. As an example: //var startTime = SomeStopwatchFunction(); cy.visit( ...

How can you apply an active class using React-Router?

My React-Router navigation issue nav.tsx import React from 'react' import { menu } from './menu' import { Link } from 'react-router-dom' import styles from './HamburgerMenu.module.scss' const HamburgerMenu: React.F ...

Cease the generation of dynamically produced sounds

I am encountering an issue in Angular where I am unable to stop playing an audio from a service. Below is my play() method: play(item: string): void { const audio = new Audio(); audio.src = item; audio.load(); audio.play(); } In order to stop all ...

What is the most effective way to handle DOM events in Angular 8?

Looking to listen for the 'storage' event from the window in Angular 8. What is the recommended approach to achieving this in Angular? window.addEventListener('storage', () => { }); One method involves using Renderer2, but are ther ...

The name 'console' could not be located

I am currently working with Angular2-Meteor and TypeScript within the Meteor framework version 1.3.2.4. When I utilize console.log('test'); on the server side, it functions as expected. However, I encountered a warning in my terminal: Cannot ...

Limiting querySelector to a specific React component: a step-by-step guide

Is there a way to target a specific DOM element within a React component to change its color using the ComponentDidMount method? Parent component export class ListComponent extends Component<...> { render(): ReactNode { return ( ...

Transform the date format from Google Forms to TypeScript

I am currently facing an issue with a Google Form connected to a Google Spreadsheet. The date format in the spreadsheet appears as follows when a response is received: 20/02/2023 18:58:59 I am seeking guidance on how to convert this date format using Type ...

How can I invoke a TypeScript function within HTML code?

Currently, I am working on an Angular project where I have implemented two straightforward methods in a TypeScript file: findForm(text: string, forms: Array<string>) { for (let form of this.forms) { if (text.includes(form)) { retur ...

Implementing debounce in Subject within rxjs/Angular2

I am currently building an events service, and here is the code snippet I am using: import { Injectable } from '@angular/core'; import { Observable } from 'rxjs/Observable'; import { Subject } from 'rxjs/Subject'; export int ...

Can a discriminated union be generated using mapped types in TypeScript?

Imagine you have an interface called X: type X = { red: number, blue: string } Can a union type Y be created using mapped types? If not, are there other ways to construct it at the type level? type Y = { kind: "red" payload: number } | ...

Is this Firebase regulation accurate and suitable for PUT and GET requests in an Angular and Firebase environment?

Creating a system where users can only see their own posts and no one else can access them is my main goal. Authentication along with posting functionality is already in place and working, but I want to implement this without using Firebase restrictions. ...

The process of running npx create-react-app with a specific name suddenly halts at a particular stage

Throughout my experience, I have never encountered this particular issue with the reliable old create-react-app However, on this occasion, I decided to use npx create-react-app to initiate a new react app. Below is a screenshot depicting the progress o ...

Error encountered during compilation while attempting to import a JSON file in Angular 7

One great aspect of angular 7 is its compatibility with typescript 3.1: https://alligator.io/angular/angular-7/ I have made the following changes to the tsconfig.json file, within the 'compilerOptions' section: "resolveJsonModule": true, "esMo ...

After the click event, the variable in the Angular .ts file does not get refreshed

Great! I have a service in my .ts component that loops through an array of court names. Every time I click on a next or back arrow event, a counter is incremented starting at 0, where index 0 corresponds to field 1 and so on. The issue I'm facing is ...

What is the best way to populate empty dates within an array of objects using TypeScript or JavaScript?

I am trying to populate this object with dates from today until the next 7 days. Below is my initial object: let obj = { "sessions": [{ "date": "15-05-2021" }, { "date": "16-05-2021" }, { "date": "18-05-2021" }] } The desired ...

Replacing `any` in TypeScript when combining interfaces

Currently using Express and attempting to explicitly define res.locals. Issue arises as in the @types/express package, Express.Response.locals is declared as any, preventing me from successfully overwriting it: types/express/index.d.ts: declare namespace ...