Utilizing Optional Generics in TypeScript

I have created a wrapper for making API calls to a Strapi server.

export const api = {
    post: async<T extends unknown, K>(url: string, body: Partial<T>, jwt?: string): Promise<K> => {
        try {
            const result = await postData<Partial<T>, K>(url, body, jwt);
            return result;
        } catch (e) {
            throw e;
        }
    },
};

My goal is to make the type K optional so I can use it like this:

 await api.post<type1, type2>(url, body);
 await api.post<type1>(url, body);

I attempted the following:

export const api = {
    post: async<T extends unknown, K = undefined>(url: string, body: Partial<T>, jwt?: string): Promise<K | T> => {
        try {
            const result = await postData<Partial<T>, K | T>(url, body, jwt);
            return result;
        } catch (e) {
            throw e;
        }
    },
};

However, this led to typing errors because either a field from type1 was missing in the return or the return object could possibly be undefined.

I am wondering if it's feasible to set it up so that if both types are used for the post function, the second type would be the return type, otherwise use the first type.

Below is a full example that can be pasted into TypeScript playground with comments on where the errors occur.

const api = {
    post: async<T extends unknown, K = undefined>(url: string, body: Partial<T>, jwt?: string): Promise<K | T> => {
        try {
            const result = await postData<Partial<T>, K | T>(url, body, jwt);
            return result;
        } catch (e) {
            throw e;
        }
    },
};

function postData<K, T>(url: string, data: K, jwt: string = '', failOnNotOk: boolean = true): T {
    const request: T = (data) as any;

    return request;
}

type user = {
    email: string;
    password: string;
}

type res = {
    valid: string;
}
(async () => {
    const url: string = 'https://google.com';
    const body: user = {
        email: '<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="4f3b2a3c3b0f2a372e223f232a612c2022">[email protected]</a>',
        password: 'test1234',
    };
    // This error occurs due to result having the possibility of being undefined
    const result = await api.post<user>(url, body);
    console.log(result.email);

    // This error occurs because 'valid' is not a field in user, when the return type should only be res
    const res = await api.post<user, res>(url, body);
    console.log(res.valid);
})();

Answer №1

Given the provided example, I would suggest updating api.post as follows:

const api = {
    post: async<T extends unknown, U = T>(
      url: string, 
      body: Partial<T>, 
      jwt?: string
    ): Promise<U> => {
        try {
            const result = await postData<Partial<T>, U>(url, body, jwt);
            return result;
        } catch (e) {
            throw e;
        }
    },
};

(I changed K to U because K typically implies key-like properties assignable to keyof any). Here, the return type is simply Promise<U>, with U defaulting to T if not specified. This provides the desired behavior:

const result = await api.post<user>(url, body);
console.log(result.email); // works
const res = await api.post<user, res>(url, body);
console.log(res.valid); // works

which I believe is what you are aiming for. Please note that while it might be a bit inconvenient to explicitly specify user instead of having the compiler infer it from the type of body, it is the best approach given TypeScript's limitations on partial type parameter inference. The available workarounds do not offer a better solution than manually specifying T.

Hopefully, this explanation helps you. Best of luck with your project!

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

"Enable the Angular Material checkbox with the value set to checked

Check out my TypeScript code below: import {Component} from '@angular/core'; @Component({ selector: 'checkbox-configuration-example', templateUrl: 'checkbox-configuration-example.html', styleUrls: ['checkbox-config ...

The response of the Typescript Subscription function

I'm struggling with retrieving the subscribe array in NG2. Being new to typescript, I find it difficult to understand how to pass variables between functions and constructors. This is what my code currently looks like: export class RosterPage exten ...

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 ...

The functionality of Layout.tsx is inconsistent across various pages

I'm having trouble with the console.log() code to display the page path only showing up in the "pages.tsx" file (src/app/pages.tsx) and not appearing in the console for other files located in (src/page/Login). Here is the code from layout.tsx: ' ...

RxJS: Understanding the issue of 'this' being undefined when used within the subscribe method

As I work on setting up a simple error notifications component and debug in Visual Studio, an issue arises within the subscribe function where 'this' is undefined. public notifications: NotificationMessage[]; constructor(notificationService: N ...

How can I change a ReactNode into a text format?

I am looking for a way to convert the following code snippet into a string while preserving Tailwind CSS and other elements. My project uses Next.js with TypeScript and Tailwind CSS. Input : export default function Header_1() { return ( <div clas ...

Can webpack effectively operate in both the frontend and backend environments?

According to the information provided on their website, packaging is defined as: webpack serves as a module bundler with its main purpose being to bundle JavaScript files for usage in a browser. Additionally, it has the ability to transform, bundle, or ...

reading an array of objects using typescript

Trying to retrieve an object from an array called pVal. pVal is the array that includes objects. I am attempting to obtain the value of "group" based on the id provided. For instance, if the id is equal to 1, it should display "VKC". Any assistance woul ...

Retrieving the input[text] value in TypeScript before trimming any special characters

One of the tasks I am working on involves a form where users can input text that may contain special characters such as \n, \t, and so on. My objective is to replace these special characters and then update the value of the input field accordingl ...

Unexpected Secondary Map Selector Appears When Leaflet Overlay is Added

Working on enhancing an existing leaflet map by adding overlays has presented some challenges. Initially, adding different map types resulted in the leaflet selector appearing at the top right corner. However, when attempting to add LayerGroups as overlays ...

Troubleshooting problems with resolving deeply nested promises

My approach to utilizing promises has been effective until now. The issue arises when the console.log(this.recipe) returns undefined and console.log(JSON.stringify(recipes)) displays an empty array. This suggests that the nested promises may not be resolvi ...

A collection of JSON data containing various diverse values

My classes are not specific and they look like this: type SyncReducerAction<TState> = (state: TState, ...args: any[]) => TState; type AsyncReducerAction<TState, TResult, TRest extends any[]> = { promise: (...args: TRest) => Promise< ...

Nest is unable to resolve DataSource in the dependency injection

I've encountered an issue with dependencies while working on my NestJS project. When I try to launch the app, the compiler throws this error at me: [Nest] 16004 - 09.04.2022, 16:14:46 ERROR [ExceptionHandler] Nest can't resolve dependencies of ...

Tips on handling multiple Redux toolkit CreateApi interceptors

I came across this solution here for implementing a reAuth baseQuery in Redux Toolkit. I have several backend services that all use the same refresh token concept. Is there a way to create a single baseQueryAuth function that can be used by multiple creat ...

Issue encountered while trying to import jszip in Angular project

Encountering an error when trying to import jszip into my TypeScript component file. After successfully running npm install jszip and confirming the package is properly installed, I proceed to import it using: import * as JSZip from 'jszip'; Th ...

Tips for addressing the error "Ensure each child in a list has a distinctive 'key' prop" in a React function using TypeScript

Encountered the following warning: Warning: Each child in a list should have a unique "key" prop. Inspect the render method of TabContext. Refer to https://reactjs.org/link/warning-keys for more details. in TabForGroupInList (at Product.tsx:148) ...

The use of the "declare" keyword is prohibited within the `script setup` section of Vue

I need to integrate the function getCookie into my Vue file. This function is already defined in the HTML file where the Vue file will be injected. <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" ...

Retrieve the observable value and store it in a variable within my Angular 13 component

Incorporating Angular 13, my service contains the following observable: private _user = new BehaviorSubject<ApplicationUser | null>(null); user$ = this._user.asObservable(); The ApplicationUser model is defined as: export interface ...

Angular 2 orderByPipe not displaying any results initially

At first, I thought the reason for not displaying anything initially was due to it not being async and returning an empty array. Everything worked fine without the pipe, but the posts weren't shown on startup. After submitting a post, all the posts ap ...

Navigate back to the initial page in Ionic2 using the navpop function

I have an application that needs to guide the user step by step. While I am aware of using navpop and navpush for navigating between pages, I am unsure about how to use navpop to go back to the first page. Currently, I am attempting to pop() twice if ther ...