Generics and Overloads in Typescript - Unable to locate specified Generics

My goal is to develop a utility function that enhances the fetch functionality within our application. One of the key aspects I want to incorporate is the ability to accept an optional Transform Function option for altering the data format returned.

type TransformFn<TResponse, TData> = (arg0: TResponse) => TData;

interface AppFetchInit extends RequestInit {
    errMsg?: string;
    transformFn?: undefined;
}

interface AppFetchInitWithTransformFn<TResponse, TData> extends Omit<AppFetchInit, 'transformFn'> {
    transformFn: TransformFn<TResponse, TData>;
}

interface AppFetchOverload<TResponse, TData = TResponse> {
    (apiURI: string, init: AppFetchInit): TResponse;
    (apiURI: string, init: AppFetchInitWithTransformFn<TResponse, TData>): TData;
}

export const appFetch: AppFetchOverload<TResponse, TData> = async (
    apiURI,
    { errMsg, transformFn, ...init } = {}
) => {
    const response = await fetch(apiURI, init);

    if (!response.ok)
        throw new Error(errMsg ?? `Fetching ${apiURI} failed: ${response.statusText}`);

    const responseJson = (await response.json()) as TResponse;

    return transformFn ? transformFn(responseJson) : responseJson;
};

While I anticipate this solution to be effective, I encounter errors related to the implementation signature, such as Cannot find name 'TResponse' and Cannot find name 'TData'. Despite being in a .ts file, which should not interpret <TResponse, TData> as jsx, these issues persist. What could be causing these errors?

Moreover, I am unsure about the correctness of the AppFetchInit and AppFetchInitWithTransformFn interfaces. For instance, does having transformFn as an optional property in AppFetchInit impact the ability to destructure it in the implementation signature? Could providing a transform function in a call potentially lead to incorrectly typing the return value?

Answer №1

It seems that the AppFetchOverload function's generic type parameters are incorrectly scoped. The current setup states that the implementer of AppFetchOverload<R, D> chooses R and D, while the caller must accept these choices. However, what you actually want is for the caller to choose R and D (or just R for the first call signature), with the implementer then handling these selections. This indicates that the call signatures should be generic, rather than the interface. Here is a revised implementation:

interface AppFetchOverload {
    <TResponse>(
      apiURI: string, init: AppFetchInit
    ): TResponse;
    <TResponse, TData>(
      apiURI: string, init: AppFetchInitWithTransformFn<TResponse, TData>
    ): TData;
}

You can now use appFetch as an instance of AppFetchOverload. Note that AppFetchOverload is no longer a generic type, so you don't need to provide type arguments directly. With this updated approach:

export const appFetch: AppFetchOverload = async (
    apiURI: string,
    { errMsg, transformFn, ...init }: 
      AppFetchInit | AppFetchInitWithTransformFn<any, any> = {}
) => {
    const response = await fetch(apiURI, init);

    if (!response.ok)
        throw new Error(errMsg ?? `Fetching ${apiURI} failed: ${response.statusText}`);

    const responseJson = (await response.json());

    return transformFn ? transformFn(responseJson) : responseJson;
};

Note that I kept the implementation loosely typed using the any type to prevent compiler errors. Implementing an overloaded function type with an arrow function presents challenges in TypeScript, leading to compromises between accuracy and flexibility. See How to correctly overload functions in TypeScript? for more details.

You can now invoke the function using either the first overload (where you need to manually specify TResponse):

const c1 = appFetch<{ x: string }>(
    "", { errMsg: "" });
// const c1: { x: string; }

Or opt for the second overload (manually specifying or inferring both types based on your requirements):

const c2 = appFetch<{ x: string }, { y: string }>(
    "", { transformFn(a) { return { y: a.x } } }
);
// const c2: { y: string; }

const c3 = appFetch("", {
    transformFn(a: { x: string }) { return { y: a.x } }
});
// const c3: { y: string; }

Check out the 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

Embedding an Angular 6 application as a JavaScript file in a pre-existing HTML website

Is it possible to seamlessly integrate an Angular 6 application into an existing HTML site by including it as a single JS file? Can you provide some insights on how to bundle an entire Angular App into just one JS file? Appreciate any help or tips. Thank ...

Promise rejection not handled: The play() function was unsuccessful as it requires the user to interact with the document beforehand

After upgrading my application from Angular 10 to 11, I encountered an error while running unit tests. The error causes the tests to terminate, but strangely, sometimes they run without any issues. Does anyone have suggestions on how to resolve this issue? ...

Displaying data-table with only the values that are considered true

Right now, I am utilizing the AgReact table to exhibit data fetched from my endpoints. The data-table is functioning properly, however, it seems to be unable to display false values received from the endpoints on the table. Below are the snippets of my cod ...

The array is populated with elements, yet the size remains zero

In my code, I utilize an array that consists of multiple subarrays and then push objects into it. The pushing process looks like this: this.storeFilesService.base64files.filetype1.push({name: file.name, base64: str}); Once I push an object into a subarr ...

Using TypeScript to import npm modules that are scoped but do not have the scope name included

We currently have private NPM packages that are stored in npmjs' private repository. Let's say scope name : @scope private package name: private-package When we install this specific NPM package using npm install @scope/private-package It ge ...

Error when running end-to-end tests in Angular CLI using the ng e2e

Recently, I created a new Angular 4 project using the Angular CLI. Upon running ng e2e, the sample end-to-end test worked flawlessly. However, when I later added a subfolder named services in the app folder and generated a service within it, the ng e2e com ...

Cordova's FileReader takes precedence over TypeScript's FileReader functionality

I encountered an issue when adding the cordova-plugin-file-transfer plugin, where I received the error message: reader.addEventListener is not a function. This problem arises due to Cordova FileReader class overriding typescript FileReader. How can this ...

Guide to effortlessly loading files with designated decorators in a node.js application

Within the realm of Project A, I have constructed a decorator and am seeking a method to automatically load all these decorators upon initializing the application in Project B, which is the project utilizing Project A. Is there a way to accomplish this tas ...

Employ the VSTS node API to retrieve all commits within a specified branch

I have been utilizing the vsts-node-api with reasonable success. However, my goal is to retrieve all commits in a specific branch, as detailed in the REST API documentation located here. Unfortunately, the node api only allows for commit queries in a rep ...

What causes the error "this condition will always return 'true'" when using an else if clause?

Exploring the concepts outlined in the handbook basics, it is noted that TypeScript is able to identify logic errors. In an attempt to experiment with this, I modified the else if section of the if...else statement as demonstrated in the handbook's ex ...

What is the best way to loop through a template literal union type in JavaScript?

Is there a way to iterate over the different values in a string union type created with template literals? type Foo = "Foo" | "Foo2" | "Foo3"; type Bar = "Bar" | "Bar2" | `${Foo}Type`; One common approach is to use a <const> assertion, like this: c ...

Having trouble deploying Firebase Cloud function following the migration to Typescript

After following the steps outlined in the firebase documentation to convert my cloud functions project to TypeScript (see https://firebase.google.com/docs/functions/typescript), I encountered an error when attempting to deploy using 'firebase deploy - ...

Error encountered in jquery.d.ts file: Unresolved syntax issue

I am currently in the process of transitioning my jQuery project into a Typescript project. I encountered an issue when attempting to include the jQuery typings from the DefinitelyTyped Github by using the following method: ///<reference path="../../ty ...

Encountered an issue: The type 'Usersinterface' is not meeting the document constraints

Below is a screenshot displaying an error: https://i.stack.imgur.com/VYzT1.png The code for the usersinterface is as follows: export class Usersinterface { readonly username: string; readonly password: string; } Next, here is the code for users ...

Guide to displaying a nested object in Angular 7

Within my object data, I have a nested structure- "data": { "serial": "123", "def": { "id": "456", "data": { "firstname&q ...

Generate a list of items in typescript, and then import them into a react component dynamically

I have a variable that stores key/value pairs of names and components in a TypeScript file. // icons.tsx import { BirdIcon, CatIcon } from 'components/Icons'; interface IconMap { [key: string]: string | undefined; } export const Icons: IconM ...

I am completely baffled by the meaning of this Typescript error

In my code, there is a button component setup like this: export interface ButtonProps { kind?: 'normal' | 'flat' | 'primary'; negative?: boolean; size?: 'small' | 'big'; spinner?: boolean; ...

Using ExpressJS with TypeScript: Understanding Request Context

In my TypeScript project, I have encountered certain restrictions when it comes to passing variables through middlewares. Initially, I tried redefining requests using interfaces, but this approach felt implicit and could potentially lead to new problems. ...

Accessing the NestJS DI container directly allows for seamless integration of dependency

I have been using NestJS for the past 4 months after working with PHP for 5 years, mainly with Symfony. With PHP, I had the ability to access the compiled DI container and retrieve any necessary service from it. For example, in an application with service ...

Discovering duplicates for properties within an array of objects in React.js and assigning a sequential number to that specific field

I am working with an array of objects where each object contains information like this: const myArr=[{name:"john",id:1}{name:"john",id:2}{name:"mary",id:3}] In the first 2 elements, the "name" property has duplicates with the value "john". How can I updat ...