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

Typescript filtering function that results in an empty array

Struggling with filtering an array in typescript and aurelia as I keep getting empty lists. For example, when searching for the keyword ra within the firstName property, I expect to retrieve the object with the name "Raja". Not sure where I'm going w ...

What is the best way to create an assertion function for validating a discriminated union type in my code?

I have a union type with discriminated properties: type Status = { tag: "Active", /* other props */ } | { tag: "Inactive", /* other props */ } Currently, I need to execute certain code only when in a specific state: // At some po ...

What is the best way to store the outcome of a promise in a variable within a TypeScript constructor?

Is it possible to store the result of a promise in a variable within the constructor using Typescript? I'm working with AdonisJS to retrieve data from the database, but the process involves using promises. How do I assign the result to a variable? T ...

What is the best way to include an item in a list with a specific identifier using the useState Record data type in a React application built with TypeScript?

Here is the structure of my Record type: const PEOPLE_MAP_INIT: Record<string, Person[]> = { "1": [], "2": [], "3": [] }; I have initialized the useState like this: const [PEOPLE_MAP, SET_PEO ...

What is the best approach to creating an array within my formgroup and adding data to it?

I have a function in my ngOnInit that creates a formgroup: ngOnInit() { //When the component starts, create the form group this.variacaoForm = this.fb.group({ variacoes: this.fb.array([this.createFormGroup()]) }); createFormGroup() ...

Toggle Button in Angular upon Form Changes

I am currently working on a bug that involves preventing users from saving data if they have not entered any information in the form. The form structure is as follows: private buildAddressPopupForm() { this.form = this.fb.group({ roles: [''], ...

Tips for correctly specifying the theme as a prop in the styled() function of Material UI using TypeScript

Currently, I am utilizing Material UI along with its styled function to customize components like so: const MyThemeComponent = styled("div")(({ theme }) => ` color: ${theme.palette.primary.contrastText}; background-color: ${theme.palette.primary.mai ...

The impact of redefining TypeScript constructor parameter properties when inheriting classes

Exploring the inner workings of TypeScript from a more theoretical perspective. Referencing this particular discussion and drawing from personal experiences, it appears that there are two distinct methods for handling constructor parameter properties when ...

Convert the Angular PrimeNG class into a TreeNode object to avoid the error of trying to access the map property of an

Currently, I am working on a project that was created with JHipster and utilizes Angular 4.3. I want to incorporate the tree component from PrimeNG into this application. My aim is to transform an array of objects into an array of TreeNodes so that it can ...

Converting Angular 2/TypeScript classes into JSON format

I am currently working on creating a class that will enable sending a JSON object to a REST API. The JSON object that needs to be sent is as follows: { "libraryName": "temp", "triggerName": "trigger", "currentVersion": "1.3", "createdUser": "xyz", ...

Upon the initial render, the fetch request in the NextJS Client component is not triggered

When I load my home page, I want to display a collection of cards from a client component. However, the API fetch request in the client component does not trigger on the initial render, preventing the cards from appearing. To fix this issue, I have to manu ...

What is the reason for TypeScript's refusal to accept this task?

In my attempt to create a type that can be A, B, or an object with a key containing an array of 2 or more items that are either A, B, or another similar object (thus allowing for recursive definition). This is the solution I came up with: type A = { p ...

Error with the type of CanvasGradient in the NPM package for converting text to image

I attempted to generate an image using a specific text by utilizing npm's text-to-image package, but encountered an error during typescript compilation. The errors I encountered upon running the typescript compilation command are related to files with ...

How do I import NPM modules in Angular 5 without using @types?

Currently experimenting with Angular 5 and beginning a project from angular-cli. I am interested in incorporating a NPM module called J2M (https://github.com/kylefarris/J2M). During my research, I came across these two commands: npm install j2m --save npm ...

Encountering a sign-in issue with credentials in next-auth. The credential authorization process is resulting in a

I am currently facing an issue with deploying my Next.js project on Vercel. While the login functionality works perfectly in a development environment, I encounter difficulties when trying to sign in with credentials in Production, receiving a 401 error st ...

Snackbar and RTK Query update trigger the error message: "Warning: Cannot update during an existing state transition."

I've built a basic ToDos application that communicates with a NodeJS backend using RTK Query to fetch data, update state, and store cache. Everything is functioning properly as expected with the communication between the frontend and backend. Recently ...

The specified type 'x' cannot be assigned to the type 'x'. Error code: 2322

I encountered an issue with the code in @/components/ui/billboard.tsx file import { Billboard } from "@/types" interface BillboardProps { data: Billboard; }; const BillboardComponent: React.FC<BillboardProps> = ({ data }) => ...

What is the proper way to declare static references in the Composition API with Typescript?

Currently, I am using the Composition API (with <script setup lang='ts'>) to create a ref that is utilized in my template: const searchRef = ref(null) onMounted(() => { searchRef.value.focus() }) Although it works and my code compiles w ...

Error encountered while attempting to resume activity: TypeError - the function `callResume` is not defined in NativeScript Angular 2

When the resume event occurs, I must invoke the method this.callResume(). However, upon calling the method, a runtime error is thrown: TypeError: this.callResume is not a function I am uncertain about how to call a method from within the resume method ...

Encountering issues with compiling files in react app using webpack, failing to compile as anticipated

When it comes to compiling, I prefer using webpack with TypeScript files. In my webpack.config.js file: module.exports = async (env, options) => { const dev = options.mode === "development"; const config = { //Webpack configuration pr ...