Using TypeScript to Limit the Generic Type Parameter Passed into a Function

I am working with a generic interface that has specific constraints applied to its parameters. Here is an example of how it is used:

interface Fetcher<Params,Resp> {
  fetch(params: Params): Resp;
};

In my code, I have a function that uses this interface with additional constraints on the generic parameters:

function paginateWithSearch<Params extends (PaginationParams & SearchParams),
  Resp extends SomeItemsResp>(fetcher: Fetcher<Params, Resp>){
    //do something      
}

The constraints are defined using simple objects like so:

type SearchParams = {
    query: string
};

However, I am facing difficulties in making these constraints work properly in practice. For instance, even when the constraints are not met, the code still runs without errors:

//sample implementation with SearchParams only
const searchFetcher: Fetcher<SearchParams, SomeItemsResp> = {
  fetch: function(): SomeItemsResp {
    throw new Error("Function not implemented.");
  }
}

//Should throw error but works ok - not extending PaginationParams
paginateWithSearch(searchFetcher);

To solve this issue, I have resorted to inferring generic parameters with conditional types and manually passing them to the function:

//now throws error if inferred manually
paginateWithSearch<FetcherParamsInfer<typeof searchFetcher>, FetcherRespInfer<typeof searchFetcher>>(
  searchFetcher
);

It seems like there might be a simpler solution that I am missing. Any input would be appreciated.

Answer №1

Despite being contrary to common sense, it functions as expected.

Keep in mind that a callback is not obliged to utilize all the given arguments or their data:

declare function convertObjToString(obj: {
  toString: () => string;
}): void;

[new Date()].forEach(convertObjToString);
// It works fine, even though it receives a Date object with additional properties

Hence, it is perfectly acceptable to pass a searchFetcher that only requires a SearchParams argument, even if it ends up receiving a

PaginationParams & SearchParams
object (which contains extra properties not necessary for the searchFetcher).

You could even provide a callback that doesn't take any arguments at all:

paginateWithSearch({
  fetch() { // No arguments needed: no problem
    return {
      items: []
    };
  }
});

Naturally, this indicates that the callback does not make use of any incoming data and will therefore return an unrelated response (such as a fixed empty array in this case).

In your scenario, it would only return information related to search and ignore pagination parameters.

It is the responsibility of the caller of paginateWithSearch to provide a sensible callback for the specific situation, whether it should involve both pagination and search, either one of them, or none.

However, it cannot utilize a callback that requires something beyond pagination and search, like sorting, for example.

paginateWithSearch({
  // Error: Property 'sorting' is missing in type 'PaginationParams & SearchParams' but required in type '{ sorting: string; }'.
  fetch(params: { sorting: string }) {
    return { items: [] };
  }
})

Access Playground Link

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

Check the type of a conditional parameter

Why isn't this code functioning properly? Could it be a case where Typescript overlooks that a variable of type (T extends '1' ? '1' : never) will never be false, making NonFalse<TypeWithCondition<T>> exactly the same ...

Error in TypeScript on SendGrid API: Invalid HttpMethod

Here is my code snippet: import sendgridClient from '@sendgrid/client' sendgridClient.setApiKey(process.env.SENDGRID_API_KEY); const sendgridRequest = { method: 'PUT', url: '/v3/marketing/contacts', bo ...

Transforming Typescript Strings into ##,## Format

As I've been using a method to convert strings into ##,## format, I can't help but wonder if there's an easier way to achieve the same result. Any suggestions? return new Intl.NumberFormat('de-DE', { minimumFractionDigits: 2, max ...

Inject a generic struct's dependencies seamlessly without the need to continually specify placeholder types or constraints

I'm currently exploring the concept of dependency injection with a generic struct and a class. My goal is to avoid having to repeatedly specify the placeholder type for the generics in both the struct and the class, as this can quickly become cumberso ...

Automatically injecting dependencies in Aurelia using Typescript

Recently, I started working with Typescript and Aurelia framework. Currently, I am facing an issue while trying to implement the @autoinject decorator in a VS2015 ASP.NET MVC 6 project. Below is the code snippet I am using: import {autoinject} from "aure ...

Mapping an array in Typescript using Angular to instantiate a class

I have received data from a web API that resembles the structure below. I am looking for guidance on how to properly map the product array into individual Products. My main objective is to convert the eating_time values into JavaScript datetime format. Cu ...

Is it feasible to obtain the parameter types of a function in Typescript?

Illustration: function a(...args: ???type of b() params???) { b(...args) } The goal is for args to match the type of parameters in function b. This idea is aimed at improving code readability and encapsulation. The function b is imported, so I don&apo ...

react useState fetch does not loop

Error: Unable to iterate over the response Welcome page: import React, {useState, useEffect} from 'react' import {Main} from './style' import {sendRequest} from './ajax/main' const Home:React.FC<any> = () => { co ...

Experiencing an issue with mui/material grid causing errors

An error occurred in the file Grid2.js within node_modules/@mui/material/Unstable_Grid2. The export 'createGrid' (imported as 'createGrid2') could not be found in '@mui/system/Unstable_Grid' as the module has no exports. Desp ...

When transmitting information to the server, the browser initiates four requests

I am encountering an issue with my React component. The problem arises when I try to retrieve the current geographic coordinates, as they are being fetched 4 times consecutively. This same glitch occurs when attempting to send the coordinates to the serv ...

Creating a list with axios in the ngOnInit lifecycle hook

I have a feature where I need to populate objects fetched from the backend. ngOnInit() { this.source.load(this.myService.findAll()); } Within myService, I am using Axios to retrieve data from the backend. I can confirm that the data is success ...

Creating a Jasmine test for the event.target.click can be accomplished by defining a spec that

I need help creating a Jasmine test spec for the following method in my component. Here is my Component Method methodName(event): void { event.preventDefault(); event.target.click(); } I have started writing a test but don't fully cover event. ...

Error occurs when trying to merge and export namespaces in TypeScript

I'm currently developing an app that utilizes TS and OpenLayers. In order to create a dependency tree, I have included import and export statements in my *.ts files. One of the files is 'openlayers.ts', which extends the base OpenLayers lib ...

When invoking the function, the original state remains unaffected within a separate function

Whenever I click on an 'item', it should establish an initial value for me to use in a comparison within another function that involves the mousemove event. However, when the mousemove function is triggered, the initial state remains at 0. imp ...

Ways in which this data can be best retrieved

Hey there, I'm currently in the process of building an Ionic2 app with Firebase integration. Within my codebase, there's a provider known as Students-services where I've written a function to navigate through a node, retrieve values, and dis ...

When my React component is tested by consumers, it cannot be located on npm

Recently, I updated a small reusable React component of mine to a new major version by adding Typescript support. It works well on my end, but a user reported an issue when trying to run Jest tests in a fresh create-react-app project after importing the co ...

Error: Issue determining the type of variable. Unable to eliminate type 'any'

I am trying to load some widgets from a template object (possibly JSON in the future). Here's an example: type RectangleTemplate = { name: 'Rectangle'; props: { width: number; height: number; } }; type ButtonTemplate = { nam ...

Loop through every item in Typescript

I am currently working with the following data structure: product: { id: "id1", title: "ProductName 1", additionalDetails: { o1: { id: "pp1", label: "Text", content: [{ id: "ppp1", label: "Tetetet" ...

Is there a way to conceal an element within a component based on the current component being used with the router?

I have managed to hide an entire component, but I am unsure of how to show or hide specific elements within a component. export class AppComponent { headerFooterVisible: boolean; constructor(private router: Router) { router.events.subscribe(e =&g ...

Experience the power of TypeScript in a serverless environment as you transform your code from

I have some JavaScript code that needs to be converted to TypeScript. Currently, I have two files: API_Responses.js const Responses = { _200(data = {}) { return { statusCode: 200, body: JSON.stringify(data), }; } }; module.export ...