Tips for setting a default value in a generic function in TypeScript, where the default argument's type is determined by the generic parameter

One of my functions calls an API and accepts a parameter to limit the fields returned by the API:

type MaximumApiResponse = {
    fieldA: string,
    fieldB: number
}

const f = async <U extends keyof MaximumApiResponse>(
    entity: number,
    props: Array<U>
): Promise<null | Pick<MaximumApiResponse, U>> => {
    return await api(entity, props);
}

I found this code snippet in the lodash source code and it works perfectly. It correctly identifies which fields the output should contain and throws a type error if you try to access a field that wasn't fetched.

However, I now want to set some default properties. I attempted this approach:

const f = async <U extends keyof MaximumApiResponse>(
    entity: number,
    props: Array<U> = ["fieldA"]
): Promise<null | Pick<MaximumApiResponse, U>> => {
    return await api(entity, props);
}

If no second argument is provided, only fieldA should be fetched. The expected return type would then be {fieldA: string}. However, this leads to an error:

Type '"fieldA"' is not assignable to type 'U'.
  '"fieldA"' is assignable to the constraint of type 'U', but 'U' could be instantiated with a different subtype of constraint 'keyof MaximumApiResponse'.

I also tried the following:

const f = async <U extends keyof MaximumApiResponse = "fieldA">(
    entity: number,
    props: Array<U> = ["fieldA"]
): Promise<null | Pick<MaximumApiResponse, U>> => {
    return await api(entity, props);
}

But this resulted in the same error. Is there a way to make both versions - one with a single argument and another with two arguments - type check properly?

I came across this link, but the solution presented there seems overly complicated for my case

Please note that I am not concerned about the function being generic; I simply need a method to restrict the output properties while also providing a default option.

Answer №1

The problem at hand has been clarified by the TS compiler, and I believe the area where you are encountering difficulty is TS's utilization of the extends keyword, which, in the context of keyof, restricts the type. The solution that you have referenced pertains to the same inquiry.

In the current state, one could execute

f<'anotherKeyOfMaxApiResponse'>(123)
, presenting a contradiction since ["fieldA"], the default value, does not correspond with an array of 'anotherKeyOfMaxApiResponse' as I have designated it using U.

Would the following approach be functional, albeit somewhat cumbersome?

const f = async <U extends keyof MaximumApiResponse = "fieldA">(
    entity: number,
    props: Array<U> | Array<Extract<keyof MaximumApiResponse, "fieldA">> = ["fieldA"]
): Promise<null | Pick<MaximumApiResponse, U>> => {
    return await api(entity, props);
}

Considering utilizing an overloaded function may be a more effective strategy, but this should suffice for now.

Answer №2

One possible solution is to use the intersection operator &.

For example, you can do something like Pick<...> & { ... }

type MaximumApiResponse = {
    fieldA: string,
    fieldB: number
}

const f = async <U extends keyof MaximumApiResponse>(
    entity: number,
    props: Array<U>
): Promise<null | Pick<MaximumApiResponse, U> & { fieldA: string }> => {
    return await api(entity, props);
}

By using this approach, you are ensuring that your return type always includes the fieldA property.

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

"Privileged" and "shared" within an Angular module

Without adding private before foo, loadBar, andtext, they are considered to be public by default in the component. export class RandomComponent { @Input() foo: string; @Output() loadBar = new EventEmitter(); text: string; } Is there any scenario whe ...

What benefits do declaration files offer compared to sources in TypeScript?

When developing and releasing a library using TypeScript, there are 2 approaches: One option is to generate declaration files d.ts along with the bundled JavaScript file and then specify it in package.json with: "types": "./dist/mylib.d.ts" Alternativel ...

Verify whether the object is properly implementing the interface

Design: export interface Person { id: number; firstName: string; lastName: string; age: number; } Is there a way to verify that an object returned from the backend aligns with the structure defined in the Person interface? ...

How come this mocha test is exceeding its timeout limit when making a basic call with mongoose?

Trying to write a simple assertion for an asynchronous database method: describe('User Repository', () => { describe('findById', () => { it('Returns a user that can be found in the database by ID', async () => { ...

Downloading a PDF in a Next.js application

How can I add a button or link that will instantly download my PDF portfolio when clicked? I am currently working on my resume section and would like to provide users with the option to easily download my CV. Is there a way to do this, and if so, how can ...

Pattern matching for validating multiple email addresses

I need assistance with validating multiple email inputs using regex in Angular. I am looking to enforce a specific format for the emails, such as: Examples: *****@zigurat.com *****@test.com *****@partlastic.com The ***** can be any characters, but the ...

React is unable to assign a class field beyond the scope of axios

class App extends React.Component { app: Application; ... componentDidMound() { axios.get(…).then(res => { this.app.currentUser = res.data.data; // setting value inside lambda function. console.log(this.app.currentUser); // ...

Leveraging Typescript Generics for limiting the input parameter of a function

Issue I have developed a Typescript package to share types between my backend node firebase cloud functions and frontend React client that accesses them. Provided below are examples of the types: interface FirstFunctionInput { x: number } interface Sec ...

Generate dynamic forms utilizing JSON data

I am in the process of developing an application that enables users to answer questions about themselves. The questions are being retrieved from an API. My next step is to generate a form with these questions as entry fields. I am currently utilizing a met ...

Adding local images to Excel can be easily accomplished using Office Scripts

Hello, I've been attempting to replace Excel cells that contain image filepaths with the actual images themselves. I found an example in Office Scripts that shows how to insert images with online URLs but doesn't mention anything about inserting ...

Struggling with the compilation of this Typescript code

Encountering a compile error: error TS2339: Property 'waitForElementVisible' does not exist on type 'signinPage' SigninPage code snippet: export class signinPage{ constructor(){ emailInput: { selector: 'input[type ...

Exploring the narrowing capabilities of TypeScript within while loops

When I write while loops, there are times when I know for sure that a certain value exists (connection in this case), but the control flow analysis is unable to narrow it down. Here's an illustration: removeVertex(vertex: string) { const c ...

Concealing forms within an Angular 5 application

I'm currently working on displaying the terms of use on the initial screen along with two buttons. If the user clicks the accept button, they will be directed to the authentication form. However, if they click refuse, the "Refused Terms" screen will a ...

Arranging a list of objects with a designated starting value to remain at the forefront

Consider the array and variable shown below: array = ['complete','in_progress','planned']; value = 'planned'; The goal is to always sort the array starting with the 'value' variable, resulting in: array ...

Develop a customized interface for exporting styled components

I am struggling to figure out how to export an interface that includes both the built-in Styled Components props (such as as) and my custom properties. Scenario I have created a styled component named CustomTypography which allows for adding typographic s ...

The error message "Type 'string' cannot be assigned to type 'Condition<UserObj>' while attempting to create a mongoose query by ID" is indicating a type mismatch issue

One of the API routes in Next has been causing some issues. Here is the code: import {NextApiRequest, NextApiResponse} from "next"; import dbConnect from "../../utils/dbConnect"; import {UserModel} from "../../models/user"; e ...

Developing Angular components with nested routes and navigation menu

I have a unique application structure with different modules: /app /core /admin /authentication /wst The admin module is quite complex, featuring a sidebar, while the authentication module is simple with just a login screen. I want to dyn ...

Issue with importing MomentJS globally in TypeScript

When it comes to defining global external modules in TypeScript, there is a useful option available. For instance, if you have jQuery library loaded externally, you can set up a global definition without having to include its duplicate in the TypeScript bu ...

Ways to expand a TypeScript interface and make it complete

I'm striving to achieve the following: interface Partials { readonly start?: number; readonly end?: number; } interface NotPartials extends Partials /* integrate Unpartialing in some way */ { readonly somewhere: number; } In this case, NotPar ...

Issues with Angular2 causing function to not run as expected

After clicking a button to trigger createPlaylist(), the function fails to execute asd(). I attempted combining everything into one function, but still encountered the same issue. The console.log(resp) statement never logs anything. What could be causing ...