What is the method for dynamically selecting generics from a function in a union type using Typescript?

Suppose there is a function defined as follows:

type FooParams<Params extends unknown[], Result> = { 
 name: string, 
 request: (...params: Params) => Promise<Result> 
}

const foo = <Params extends unknown[], Result>(params: FooParams<Params, Result>) => {
  // do stuff
}

Now imagine there are a couple of requests and a collection called "store" that holds these requests:

interface Todo {
  id: number;
  title: string;
}

const getTodos: () => Promise<Todo[]> = () => Promise.resolve([{ id: 2, title: 'clean' }]);

const getTodo: (id: number) => Promise<Todo> = (id: number) => Promise.resolve({ id, title: 'clean' });

const requestStore = {
  getTodo: {
    name: 'getTodo',
    request: getTodo,
  },
  getTodos: {
    name: 'getTodos',
    request: getTodos,
  },
} as const;

The objective now is to generate "foo"-functions for each request in the store.

While adding them manually with specific keys works:

// Works
foo(requestStore['getTodo'])

Attempting to add them dynamically like this does not work:

// Does not work. Error message:
// Type '(() => Promise<Todo[]>) | ((id: number) => Promise<Todo>)' is not assignable to type '() => Promise<Todo[]>'.
//  Type '(id: number) => Promise<Todo>' is not assignable to type '() => Promise<Todo[]>'.(2322)
const createFooFromStore = (requestName: keyof typeof requestStore) => () => {
  const { name, request } = requestStore[requestName]
  foo({ name, request })
}

Is there an alternative approach to rewrite this so that a foo-function can be created for every entry in the "requestStore"?

Here is a link to a playground with the sample code:

Playground

In the playground example, the error message is displayed at the bottom for the "request" parameter.

Answer №1

Unfortunately, I am unable to share the playground link in the comments section, but you can access it by clicking on this playground

// By utilizing a type constraint here, we are able to ensure the proper functionality of the code.
// This allows specific keys from the RequestStore as valid inputs for foo
const foo = <T extends RequestStore[keyof RequestStore]>(params: T) => {
  // Returning params to check type inference
  return params
}

interface Todo {
  id: number;
  title: string;
}

const getTodos: () => Promise<Todo[]> = () => Promise.resolve([{ id: 2, title: 'clean' }]);

const getTodo: (id: number) => Promise<Todo> = (id: number) => Promise.resolve({ id, title: 'clean' });

// If you want to ensure type safety, you can use the 'satisfies' keyword to prevent incorrect request definitions, although it is not mandatory

const requestStore = {
  getTodo: {
    name: 'getTodo',
    request: getTodo,
  },
  getTodos: {
    name: 'getTodos',
    request: getTodos,
  },
} as const satisfies Record<string, { name: string, request: (...args: any[]) => any }>;
type RequestStore = typeof requestStore

// Everything functions correctly
foo(requestStore['getTodo'])
foo(requestStore['getTodos'])


const createFooFromStore = <T extends keyof typeof requestStore>(requestName: T) => () => foo(requestStore[requestName])

const a = createFooFromStore("getTodo") // valid
const b = createFooFromStore("getTodos") // valid
const c = createFooFromStore("getTos") // invalid

const createFooFromStore2 = <T extends keyof typeof requestStore>(requestName: T) => () => { foo(requestStore[requestName]) }

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

Can you guide me on how to record a value in Pulumi?

According to Pulumi's guidance on inputs and outputs, I am trying to use console.log() to output a string value. console.log( `>>> masterUsername`, rdsCluster.masterUsername.apply((v) => `swag${v}swag`) ); This code snippet returns: & ...

What is the best way to fill the dropdown options in every row of a data table?

This HTML snippet displays a data table with test data and corresponding dropdown options. <tr> <th> Test Data </th> <th> Test Data ...

Error: Uncaught TypeError in AuthContext in Next.js 13.0.6 when using TypeScript and Firebase integration

I'm currently trying to display a page only if the user is present in my app. Right now, the app is pretty bare bones with just an AuthContext and this one page. I had it working in React, but ran into some issues when I switched it over to TS and Nex ...

Tips for including a clickable button in an Angular textarea

I am looking for a solution to float a button to the top right corner of a text area in an Angular application. Below is my current code setup, but unfortunately, it does not achieve the desired result: <textarea matInput matInput rows="15" cols="40" ...

Guide on creating a detailed list of categories mapped to specific classes that all adhere to a common generic standard

Most TypeScript factory patterns I've encountered rely on a named mapping between a name and the Class type. A basic implementation example: const myMap = { classOne: ExampleClass, classTwo: AnotherClass } (k: string) => { return new myMap[k] } ...

Troubleshooting Date Errors in Typescript with VueJS

Encountering a peculiar issue with Typescript while attempting to instantiate a new Date object. <template> <div> Testing Date</div> </template> <script lang="ts"> import Vue from "vue"; export default Vue.extend({ name: ...

Converting an array into an object using Typescript and Angular

I have a service that connects to a backend API and receives data in the form of comma-separated lines of text. These lines represent attributes in a TypeScript class I've defined called TopTalker: export class TopTalker { constructor( pu ...

Angular function for downloading table cells

I'm working with a table containing objects and I need to download each one by clicking on a download button. Download Img <wb-button name="submitButton" variant="primary" size="s" style ...

Angular is not providing the anticipated outcome

I'm new to Angular (7) and I'm encountering an issue while trying to retrieve the status code from an HTTP request. Here's the code snippet used in a service : checkIfSymbolExists() { return this.http.get(this.url, { observe: 'res ...

Error message "After the upgrade to Angular 15, the property 'selectedIndex' is not recognized in the type 'AppComponent'."

My Ionic 6 app with capacitor has been updated in the package.json file. These are the changes: "dependencies": { "@angular/common": "^15.1.0", "@angular/core": "^15.1.0", "@angular/forms": "^15.1.0", "@angular/platform-browser": "^15.1. ...

The error message "Uncaught TypeError: emit is not a function in Vue 3" indicates

As I implemented the code in the Vue 3 setup block to retrieve the input value according to this answer, here is a snippet of the code: import { defineComponent } from "vue"; import { defineProps, defineEmits } from 'vue' export defaul ...

Differences between tsconfig's `outDir` and esbuild's `outdir`Explanation of the variance in

Is there a distinction between tsconfig's outDir and esbuild's outdir? Both appear to accomplish the same task. Given that esbuild can detect the tsconfig, which option is recommended for use? This query pertains to a TypeScript library intended ...

Angular Error TS2339: The property 'car' is missing from type 'Array of Vehicles'

Encountering Angular Error TS2339: Property 'vehicle' is not found on type 'Vehicle[]'. The error is occurring on data.vehicle.results. Any thoughts on what could be causing this issue? Is the problem related to the Vehicle model? I hav ...

In TypeScript, the first element of an array can be inferred based on the second element

Let's consider a scenario where we have a variable arr, which can be of type [number, 'number'] or [null, 'null']. Can we determine the type of arr[0] based on the value of arr[1]? The challenge here is that traditional function ov ...

One typical approach in React/JavaScript for monitoring the runtime of every function within a program

Experimenting with different techniques such as performance.now() or new Date().getTime() has been done in order to monitor the processing time of every function/method. However, specifying these methods within each function for time calculation purposes h ...

We are in need of a provider for the Ionic Network native plugin

I have encountered an issue while trying to use Ionics native plugin "Network" as it fails due to a missing provider. To prevent any errors, I performed a fresh installation of Ionic along with the necessary dependencies: ionic cordova plugin add cordova- ...

Changing the default font size has no effect on ChartJS

I'm trying to customize the font size for a chart by changing the default value from 40px to 14px. However, when I set Chart.defaults.global.defaultFontSize to 14, the changes don't seem to take effect. Below is the code snippet for reference. An ...

Tips for choosing an HTML element using Playwright with TypeScript

My goal is to use playwright with typescript in order to select a specific html element. The element I am trying to target has the class "ivu-select-dropdown" and a specific style. <div class="ivu-select-dropdown" style="position: absolut ...

Typescript can represent both optional and required generic union types

Purpose My goal is to establish an optional parameter unless a specific type is provided, in which case the parameter becomes mandatory. Desired Outcome I aim for the get method below to default to having an optional parameter. However, if a type TT is p ...

Leverage the power of TypeScript by enabling the noImplicitAny flag when working

Currently, I am looking to activate the noImplicitAny flag in my compiler. My main issue lies with utilizing lodash/fp as there are no typings available at this moment. Due to this, the compiler is generating errors due to the absence of a definition file ...