Steps for assigning a parameter to a general purpose function

Having a generic function named retrieve:

function retrieve<T>(endpoint: string, id: string, /* etc */): T {}

The goal is to define a function like retrieveUser, which binds the first parameter and specifies T.

An attempt was made using Function.prototype.bind():

const retrieveUser = retrieve.bind(/* this */ undefined, "/user");

However, there was an issue with specifying T.

Another approach involved rest parameters:

type User = { username: string, /* etc */ };
const retrieveUser = (...args) => retrieve<User>("/user", ...args);

This method achieves the intended result but Typescript raised some complaints:

  • error TS7019: Rest parameter 'args' implicitly has an 'any[]' type.
  • error TS2556: A spread argument must either have a tuple type or be passed to a rest parameter.

The question arises whether it's possible for Typescript to infer types of args from the retrieve function. Is there a more efficient way to achieve this objective?

Answer №1

TypeScript doesn't natively support the specific type of higher-order generic function manipulation you're looking for in this scenario. It would be ideal to specify "use T in typeof retrieve with User", but unfortunately, there isn't a straightforward way to achieve this within the language. Perhaps if TypeScript included features like higher-kinded types or generic values, as requested in microsoft/TypeScript#1213 or microsoft/TypeScript#17574, this could be done more smoothly.


To work around this limitation without being ad-hoc, you can explicitly define the type "specify T in typeof retrieve with User", annotate a variable with that type and assign retrieve to it:

const userRetrieve: (endpoint: string, id: string, /* etc */) => User = retrieve; // okay

The compiler accepts this approach, indicating that although programmatically specifying T with

User</code in the type system may not be easy, it recognizes that <code>retrieve
is compatible with the intended type. This maintains type safety by flagging any errors if the type is incorrect.

Subsequently, you can bind a parameter to userRetrieve:

const retrieveUser = userRetrieve.bind(undefined, "/user");
// const retrieveUser: (id: string) => User

If the parameters of retrieve are non-generic, you can utilize the Parameters<T> utility type to streamline the process:

const userRetrieve: (...args: Parameters<typeof retrieve>) => User = retrieve; // okay
const retrieveUser = userRetrieve.bind(undefined, "/user");
// const retrieveUser: (id: string) => User

In situations where expedience outweighs strict type safety, you can replace the annotated variable with a type assertion:

const retrieveUser =
  (retrieve as (...args: Parameters<typeof retrieve>) => User).
    bind(undefined, "/user");

This concise method sacrifices some level of type safety for convenience.


If the parameters rely on the generic type parameter, using Parameters<T> will not retain track of any type parameter:

declare function retrieve<T>(endpoint: string, id: string, somethingGeneric: T): T;

const retrieveUser =
    (retrieve as (...args: Parameters<typeof retrieve>) => User).
        bind(undefined, "/user");
// const retrieveUser: (id: string, somethingGeneric: unknown) => User 

In this case, where the type is mistakenly inferred as unknown instead of

User</code, manual intervention is necessary to rectify this discrepancy:</p>
<pre><code>const retrieveUser =
    (retrieve as (endpoint: string, id: string, somethingGeneric: User) => User).
        bind(undefined, "/user");
// const retrieveUser: (id: string, somethingGeneric: User) => User

While there are ways to leverage limited support for higher order generic function inference to automate inserting T, it's quite complex and esoteric. For further insights into this method, refer to this answer.

class GenTypeMaker<T> {
    getGenType!: <A extends any[], R>(cb: (...a: A) => R) => () => (...a: A) => R;
    genType = this.getGenType(null! as typeof retrieve)<T>()
}
type Retrieve<T> = GenTypeMaker<T>['genType']
// type Retrieve<T> = (endpoint: string, id: string, somethingGeneric: T) => T

With careful specification, the type for Retrieve<T> aligns with the function type of retrieve</code when <code>T is designated. Thus, Retrieve<User> encompasses the desired type:

const retrieveUser =
    (retrieve as Retrieve<User>).
        bind(/*this*/ undefined, "/user");
// const retrieveUser: (id: string, somethingGeneric: User) => User

This technique is intriguing but not recommended for everyday use. In cases like these, it's advisable to manually declare and annotate the appropriate types for clarity and accuracy.

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

Dealing with TypeScript and the Mongoose loadClass problem

Working with Mongoose's class schemas has been very beneficial for me. Incorporating TypeScript into my Node project has enhanced the development process. I made sure to refer to Mongoose the Typescript way...? in order to ensure that my Model align ...

React: The useContext hook does not accurately reflect the current state

I'm currently facing a dilemma as I attempt to unify data in my app. Whenever I click the button, the isDisplay value is supposed to be set to true; even though the state changes in my context file, it does not reflect in the app. Thank you for your ...

Unable to locate the next/google/font module in my Typescript project

Issue With Import Syntax for Font Types The documentation here provides an example: import { <font-name> } from 'next/google/font'; This code compiles successfully, but throws a "module not found" error at runtime. However, in this disc ...

Tips on implementing computed properties in Vue.js while using TypeScript

There is a significant amount of documentation on how to utilize Vue.js with JavaScript, but very little information on using TypeScript. The question arises: how do you create computed properties in a vue component when working with TypeScript? According ...

Uniting 2 streams to create a single observable

I am in the process of merging 2 different Observables. The first Observable contains a ShoppingCart class, while the second one holds a list of ShoppingItems. My goal is to map the Observable with shopping cart items (Observable<ShoppingItems) to the i ...

Tips for disentangling code from types in Typescript

Instead of intertwining code and types like the example below: const compar8 : boolean | error = (action: string, n: number) => { switch(action) { case 'greater': return n > 8; case 'less': ...

Unable to successfully remove item using Asyncstorage

const deleteProduct = prod => { Alert.alert( 'Delete Product', `Are you sure you want to remove ${prod.id}?`, [ { text: 'Cancel', style: 'cancel', }, { ...

What is the procedure for accessing a namespace when declaring it globally?

Website Project Background Currently, I am working on a simple website where users can update their pictures. To achieve this functionality, I am utilizing the Multer library along with Express in Typescript. Encountered Issue I am facing a challenge re ...

Problems arising from the layout of the PrimeNG DataView component when used alongside Prime

I've been working with a PrimeNG DataView component that requires the use of PrimeFlex's flex grid CSS classes to set up the grid structure. One of their examples includes the following instructions: When in grid mode, the ng-template element ...

The provided Material-UI Fade component contains multiple children, which is not supported by 'ReactElement<any, any> | undefined'

I'm struggling to implement a Material UI <Fade> component in my code. Unfortunately, I keep encountering the following error message and as someone who is still learning TypeScript, I am unsure of how to resolve it. Error: Expected ReactElement ...

What's stopping me from using useState() to assign API data to an array?

I have a question regarding my app that makes use of the Movies API. I am making an API request and then passing the data to an array using the useState hook. Here is a snippet of my code: const App = () => { type MovieType = { rate: string, ...

Is it necessary for me to set up @types/node? It appears that VSCode comes with it pre-installed

Many individuals have been adding @types/node to their development dependencies. Yet, if you were to open a blank folder in VSCode and create an empty JavaScript file, then input: const fs = require('fs'); // <= hover it and the type display ...

Merging all Angular 2 project files into a single app.js document

I've scoured the depths of the internet for an answer to this burning question: How can I merge all my Angular 2 code, along with its dependencies, into a single file? Although this query has been posed countless times before, I bring a fresh perspect ...

Using Lerna with Docker for Next.js and GraphQL applications

Currently, I am working with lerna and everything runs smoothly locally. However, when I attempt to build the image and operate it through Docker, it does not function as expected. FROM node:16-alpine3.11 ENV NODE_ENV=production COPY . /app WORKDIR /app R ...

The Typescript object may be null even with its initial value set

1: let a: Record<string, any> | null = {}; 2: a['b'] = 2; Encountered the TS2531: Object is possibly 'null' error on Row 2 despite having an initial value. To address this issue, the code was updated as follows: 1: let a: Record ...

Encountering a JavaScript/TypeScript issue that reads "Unable to access property 'Items' as it is undefined"

I encountered an issue with Javascript where I'm receiving an error message stating "Cannot read property 'Items' of undefined". The this keyword is consistently showing as undefined in the Base class. How can this problem be resolved? Coul ...

What is the preferred build tool to use with Deno?

It has come to my attention that deno no longer necessitates the use of package.json (compatible with npm/yarn) to detail its dependencies. However, when it comes to build/run scripts, is package.json still the recommended descriptor or are there alternat ...

Encountering ExpressionChangedAfterItHasBeenCheckedError in Angular 17 even after invoking detectChanges method

I'm encountering a minor problem with Angular and its change detection mechanism. I have created a simple form where additional input fields can be added dynamically. However, every time I click the add button, an ExpressionChangedAfterItHasBeenChecke ...

The use of 'import ... =' is restricted to TypeScript files

Error: Oops! Looks like there's a hiccup in the code... 'import ... =' is exclusive to TypeScript files. Expecting '=' here. Don't forget the ';'. Unexpected keyword or identifier popping up! package.json ...

In the context of Angular, the ELSE statement continues to run even after the IF condition has been satisfied within

Currently, I am utilizing Angular 11 in conjunction with Firestore. Within my code, I am fetching data using the subscribe method from an API service. Subsequently, I am employing a for loop to extract object values in order to verify if a value within a c ...