Discovering various kinds of data with a single generic type

I am looking to define a specific type like this:

type RenderItems<T> = {
    [K in keyof T]: {
        label: string;
        options: {
            defaultValue: T[K]['options'][current_index_of_array];
            item: (value: T[K]['options'][current_index_of_array]) => void;
        }[];
    }
}

This allows me to use the following function:

const func: <T>(renderItems: RenderItems<T>) => void = (renderItems) => {}

func({
    prop1:{
        label:"example",
        options:[
            {
                defaultValue: "example string",
                item(value){
                    // The value is expected to be of type string
                }
            },
            {
                defaultValue: new Date(),
                item(value){
                    // The value is expected to be of type Date
                }
            }
        ]
    },
    prop2:{
        label:"example 2",
        options:[
            {
                defaultValue: 1234
                item(value){
                    // The value is expected to be of type number
                }
            },
        ]
    },
})

Each item function in the options array will automatically have its type recognized based on the assigned defaultValue

Answer №1

If you need to specify the desired type, you can do so using the following syntax:

type RenderItems<T extends Record<keyof T, readonly any[]>> = {
  [K in keyof T]: {
    label: string;
    options: XForm<T[K]>
  }
}

type XForm<T extends readonly any[]> =
  { [I in keyof T]: { defaultValue: T[I], item: (value: T[I]) => void } }

const func: <const T extends Record<keyof T, readonly any[]>>(
  renderItems: RenderItems<T>
) => void = (renderItems) => { }

The XForm<T> utility type utilizes an array type argument for T and employs a mapped type over tuple/arrays to iterate over the numeric indices I, mapping each element individually. This way, I acts as the equivalent of your current_index_of_array, resulting in a processed array without needing additional brackets [].


However, this approach may not serve your specific intent. TypeScript lacks the capability to automatically infer parameter types for context-sensitive callback functions nested within a generic array/object structure like this:

func({
  prop1: {
    label: "example",
    options: [
      {
        defaultValue: "example string",
        item(value) {
          // ^?(parameter) value: any 😢
        }
      },
      {
        defaultValue: new Date(),
        item(value) {
          // ^?(parameter) value: any 😢
        }
      }
    ]
  },
  prop2: {
    label: "example 2",
    options: [
      {
        defaultValue: 1234,
        item(value) {
          //^? (parameter) value: any 😢
        }
      },
    ]
  },
});

While potential solutions are being discussed, such as microsoft/TypeScript#53018, it is unlikely that this behavior will change soon. Therefore, this method might not yield the expected results at present.


If achieving inference for those callback parameters is essential, it appears to be more of an XY problem. Defining RenderItems<T> alone won't suffice. A different strategy involves breaking down the task into smaller segments for better compiler understanding. For instance, individual elements within options could result from function outputs with the sole purpose of validating a single pair of defaultValue and item:

interface Opt<T> {
  defaultValue: T,
  item(value: T): void;
}
function opts<T>(defaultValue: T, item: (x: T) => void): Opt<T> {
  return { defaultValue, item }
}

Subsequently, define func() as follows:

const func: <const T extends
  Record<keyof T, {
    label: string, options: readonly Opt<any>[]
  }>>(
    renderItems: T
  ) => void = (renderItems) => { }

This allows calling func() like this:

func({
  prop1: {
    label: "example",
    options: [
      opts("example string", value => { }),
      //                     ^?(parameter) value: string
      opts(new Date(), value => { })
      //               ^?(parameter) value: Date
    ]
  },
  prop2: {
    label: "example 2",
    options: [
      opts(1234, value => { }),
      //         ^?(parameter) value: number
    ]
  },
});

Consequently, inference occurs precisely where needed, deducing a type T structured as:

{
    readonly prop1: {
        readonly label: "example";
        readonly options: readonly [Opt<string>, Opt<Date>];
    };
    readonly prop2: {
        readonly label: "example 2";
        readonly options: readonly [Opt<number>];
    };
}

It presents a workaround solution, despite the manual addition of opts(); yet ensures successful inference!

Explore the code on the TypeScript Playground

Answer №2

In some cases, Typescript may struggle to deduce the correct type for a value but can be configured to throw an error if provided with the incorrect type. For instance, consider defining a RenderItems as shown below:

type RenderItems<T extends Record<string, { options: unknown[] }>> = {
    [K in keyof T]: {
        label: string;
        options: (
                T[K]["options"][number] extends infer O ?
                O extends {defaultValue: infer V} ?
                {
                    defaultValue: V;
                    item: (value: V) => void;
                } : never : never
            )[]
        }
    }

If we attempt to use the following code snippet, it will produce a helpful error message:

Type (value: string) => void is not assignable to type (value: number) => void.

declare const func: <T extends RenderItems<T>>(renderItems: T) => void

func({
    prop2:{
        label:"example 2",
        options:[
            {
                defaultValue: 1234,
                item(value: string){
                }
            },
        ]
    },
})

Note that we modified the type of func from

<T>(items: RenderItems<T>) => void
to
<T extends RenderItems<T>>(items: T) => void
, which enables us to extract the value types using
O extends {defaultValue: infer V}
and subsequently utilize V to define item as item: (value: V) => void;

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

Transferring information from child to parent class in TypeScript

I have a scenario where I have two classes (Model). Can I access properties defined in the child class from the parent class? Parent Class: class Model{ constructor() { //I need the table name here. which is defined in child. } publ ...

Failure of React to connect event handlers

LATEST UPDATE: After removing the output entry from my webpack configuration, the React event listeners are now functioning correctly. Currently, I am diving into the world of hand-rolling webpack configurations for a React/TypeScript application for the ...

Having trouble retrieving image information within the Asp.net core controller

I am facing an issue trying to store image details in the database through Angular and ASP.NET Core. I am unable to retrieve the image data sent from Angular in the controller. Although I am able to obtain the image information using the [FromForm] attribu ...

Troubleshooting a Custom Pipe Problem in Angular Material Drag and Drop

Currently, I am working on a project involving Angular Material Drag And Drop functionality. I have created a simplified example on StackBlitz which you can access through this link: here The project involves two lists - one containing pets and the other ...

Explore one of the elements within a tuple

Can we simplify mapping a tuple element in TypeScript? I'm seeking an elegant way to abstract the following task const arr: [string, string][] = [['a', 'b'], ['c', 'd'], ['e', 'f']] const f ...

Next.js is refusing to render an array of HTML elements

Consider this scenario where I have a block of code in TypeScript that attempts to create and display a list of elements. Here is a sample implementation: const MenuList = ():ReactElement => { const router = useRouter(), liElements:any = []; con ...

Adding a QR code on top of an image in a PDF using TypeScript

Incorporating TypeScript and PdfMakeWrapper library, I am creating PDFs on a website integrated with svg images and QR codes. Below is a snippet of the code in question: async generatePDF(ID_PRODUCT: string) { PdfMakeWrapper.setFonts(pdfFonts); ...

Set a value to the field name within a variable in TypeScript

Can anyone help me with this problem? type A { f1: string; f2; string; } I have a variable that holds the name of a field: let fieldName: string = "f2"; I want to create an object using the fieldName: {"content of fieldName": "sdf"} Any suggestio ...

The DAT GUI controls are mysteriously absent from the scene

Within a modal, I have set up a threejs scene with three point lights. All functions are exported from a separate file called three.ts to the modal component. The issue I am facing is that when I try to initialize DAT.GUI controls, they end up rendering ...

Unable to locate the reference to 'Handlebars' in the code

I am currently attempting to implement handlebars in Typescript, but I encountered an error. /// <reference path="../../../jquery.d.ts" /> /// <reference path="../../../require.d.ts" /> My issue lies in referencing the handlebars definition f ...

What is the best way to structure a nested object model in Angular?

Issue occurred when trying to assign the this.model.teamMembersDto.roleDto to teamMembersDto. The error message states that the property roleDto does not exist on type TeamMembersDropdownDto[], even though it is nested under teamMembersDto. If you look at ...

Ensure that the Promise is resolved upon the event firing, without the need for multiple event

I'm currently working on a solution where I need to handle promise resolution when an EventEmitter event occurs. In the function containing this logic, an argument is passed and added to a stack. Later, items are processed from the stack with differe ...

Incorporating node packages into your typescript projects

I have been exploring various discussions on this forum but I am still unable to make it work. My goal is to compile the following code in TypeScript. The code is sourced from a single JavaScript file, however, due to issues with module inclusion, I am foc ...

Creating an index signature in TypeScript without having to use union types for the values - a comprehensive guide

Is it possible to define an index signature with strict type constraints in TypeScript? interface Foo { [index: string]: number | string } However, I require the value type to be either number or string specifically, not a union of both types (number | ...

What is the process for transforming a multi-dimensional array containing strings into a multi-dimensional array containing numbers?

I've got a unique structure of data composed of arrays with strings as seen below: [ 0: Array(1) 0: Array(6) 0: [5.379856, 43.252967] 1: [5.422988, 43.249466] 2: [5.425048, 43.245153] 3: [5.383804, 43.239 ...

No updates found (Angular)

When a button is clicked, a test method is triggered with i as the index of an element in an array. The test method then changes the value of the URL (located inside the sMediaData object) to null or '' and sends the entire sMediaData to the pare ...

Ways to insert script tag in a React/JSX document?

private get mouseGestureSettingView() { const {selectedMenu} = this.state; return ( selectedMenu == 2 ? <script src="../../assets/js/extensions/mouse-gesture/options.js"></script> <div className={styles.settingForm}& ...

What is the best way to inject services into non-service class instances in Angular 2?

Here is my current approach, but I'm curious about the recommended practice for working with Angular2? ... class MultitonObject { _http: Http; constructor (appInjector: Injector) { this._http = appInjector.get(Http); } } var ap ...

Elevated UI Kit including a setFloating function paired with a specialized component that can accept

I am currently experimenting with using Floating UI alongside various custom React components. These custom components create internal element references for tasks like focus and measurements, but they also have the capability to accept and utilize a RefOb ...

Creating autorest client based on various OpenAPI versions

I'm currently exploring options for creating a Typescript client from our .NET API. After researching various tools, I decided to go with Autorest, as it is Node-based and fits my skillset. While I am aware of Swashbuckle, my knowledge leans more towa ...