Dynamic typing depending on the individual elements within an array

I am working with some extensions:

type ExtensionOptions = { name: string }

type BoldOptions = ExtensionOptions & { boldShortcut?: string }
type ItalicOptions = ExtensionOptions & { italicShortcut?: string }
type LinkOptions = ExtensionOptions & { autoLink?: boolean }

declare class Extension {
    constructor(options: ExtensionOptions)
}

declare class BoldExtension extends Extension {
  isSelectionBold: boolean
  constructor(options: BoldOptions)
}

declare class ItalicExtension extends Extension {
  isSelectionItalic: boolean
  constructor(options: ItalicOptions)
}

declare class LinkExtension extends Extension {
  isSelectionLink: boolean
  constructor(options: LinkOptions)
}

My goal is to define an Array type that allows passing Tuple containing an extension and its options with intellisense support. For example:

// Generic type. It currently lacks intellisense
type Extensions = Array<[Extension, ExtensionOptions]>

const extensions = [
  [BoldExtension, { name: 'bold' }],
  [ItalicExtension, { name: 'italic' }],
  [LinkExtension, { name: 'link', autoLink: true }] // Intellisense needed for LinkExtension here
]

I attempted some experiments:

type Extensions<T> = Array<[
  T,
  T extends new (...args: infer Options) => any
    ? Options[0] // first argument should be the `options`
    : never
]>

Here's the TypeScript playground link for reference.

Answer №1

Unfortunately, TypeScript does not provide a straightforward ExtensionPair type that fits your requirements. Instead, you need a tuple type with two elements: the first element should be a one-argument construct signature that generates an instance of some Extension, and the second element should be the type of its argument. This relationship between the elements necessitates the use of generics for expression. If TypeScript supported existentially quantified generics (as mentioned in microsoft/TypeScript#14466), you could define "some" more explicitly in the code to make ExtensionPair a distinct type. However, since this feature is lacking, you have to resort to using regular generics, which are universally quantified and encompass "all" instead of just "some":

type ExtensionPair<T extends new (arg: any) => Extension> = 
  [T, ConstructorParameters<T>[0]];

If you possess an array or tuple of these pairs, you must also have a corresponding set of type arguments. You can represent this as a mapped array/tuple type, where keys iterate over number-like indices only:

type ExtensionPairs<T extends (new (arg: any) => Extension)[]> = 
  { [I in keyof T]: ExtensionPair<T[I]> }

While defining such a type might seem cumbersome due to redundant information, it's crucial for accuracy. To streamline the process, consider leveraging a generic helper function that allows TypeScript to infer type arguments during calls:

function asExtensionPairs<T extends (new (arg: any) => Extension)[]>(
  arr: [...ExtensionPairs<T>]) {
  return arr;
}

This approach simplifies the declaration of extensions by inferring type automatically based on provided inputs, enhancing the development experience with autosuggest/IntelliSense features and error detection:

const extensions = asExtensionPairs([
  [BoldExtension, { name: 'italic' }],
  [ItalicExtension, { name: "" }],
  [LinkExtension, { name: "a", autoLink: true }]
]);
/* const extensions: [
    ExtensionPair<typeof BoldExtension>, 
    ExtensionPair<typeof ItalicExtension>, 
    ExtensionPair<typeof LinkExtension>
  ] */

To explore the sample code further or test different scenarios, visit the 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

Error with TypeScript Compiler in Angular 2

Currently, I am facing an issue while trying to run tsc in my Angular 2 application directory. The error message I receive is: Error TS5023: Unknown compiler option 'moduleResolution'. This issue seems to be hindering the startup process, as ts ...

Is it possible to incorporate regular React JSX with Material UI, or is it necessary to utilize TypeScript in this scenario?

I'm curious, does Material UI specifically require TypeScript or can we use React JSX code instead? I've been searching for an answer to this question without any luck, so I figured I'd ask here. ...

Having trouble resolving Typescript module references

My dilemma revolves around two Typescript react modules known as moduleA and moduleB. My attempt involves utilizing a component called Button.tsx from moduleA by exporting it in moduleB and referencing this component through moduleA. These are the steps ...

Encountering an issue with top-level await in Angular 17 when utilizing pdfjs-dist module

While using the Pdfjs library, I encountered an error message that reads: Top-level await is not available in the configured target environment ("chrome119.0", "edge119.0", "firefox115.0", "ios16.0", "safari16.0" + 7 overrides) /****/ webpack_exports = g ...

What causes functions operating on mapped objects with computed keys to not correctly infer types?

If you are seeking a way to convert the keys of one object, represented as string literals, into slightly modified keys for another expected object in Typescript using template string literals, then I can help. In my version 4.9.5 implementation, I also ma ...

Is there a way to access the [class.editable] value during an Angular unit test?

For my unit test, I am trying to retrieve the value of [class.editable]. <div class="coolcomponent layout horizontal center" [class.editable]=editable> ..... </div> When using fixture.nativeElement.querySelector('editable');, my e ...

Exploring API information in Ionic 4

Looking to retrieve data from the API, specifically using PHP on the backend. While I can access the data successfully, I'm running into an issue with *ngFor and the search bar functionality. The search button only appears when the input in the search ...

Next.js experiencing hydration error due to dynamic component

Having an issue with my RandomShape component, where it should display a random SVG shape each time the page is reloaded. However, I am encountering a hydration error on the client side. import React from "react"; import shapes, { getRandomShape ...

The type 'Readonly<Ref<Readonly<any>>>' does not have the property 'forEach' available

Having an issue with state management in Vue3 (Pinia) using the Composition API. I successfully retrieved an array called countryCodes and now I want to copy all items from this array into another array called countries defined in the state. However, whe ...

Error: The module '@angular/core' cannot be located

Currently, I am working on a simple Angular 2 project with NodeJS as the backend and my preferred editor is Atom. So far, I have successfully installed Angular2 (2.0.0-beta.17) and Typescript using npm. npm install angular2 npm install -g typescript Wit ...

Angular: ngx-responsive has a tendency to hide elements even if they meet the specified conditions

Recently, I started using a library called this to implement various designs for desktop and mobile versions of an Angular app (v4.2.4). Although the documentation recommends ngx-responsive, I opted for ng2-responsive but encountered issues. Even after set ...

Customizable parameters in a React component

I am encountering two issues with the code provided below: interface MyForm { age: number; email: string; name: string; } function Form< T, ComponentProps extends { name: string; onChange: (event: React.ChangeEvent) => void; } &g ...

I'm struggling to include a link in my project card component - I've tried using both the Link tag and anchor tag, but so far, I haven't been successful in

I am having trouble getting the link tag to work properly in my UI. I have tried using both the link and anchor tags, but neither seems to be functioning as expected. Can someone please advise on how to fix this issue? https://i.sstatic.net/tAD7C.png I w ...

In Typescript, we can streamline this code by assigning a default value of `true` to `this.active` if `data.active

I am curious if there is a better way to write the statement mentioned in the title. Could it be improved with this.active = data.active || true? ...

How to separate an array of objects into individual arrays using Typescript reduce based on a specific property

I have the following array: statisticsOfScrapDeliveriesItems:[ { supplierId: "0001055404", deliveredFrom: "METALLCO AS", centerId: "C45", materialId: "TS0180", }, { sup ...

Steps for integrating a valid SSL certificate into a Reactjs application

After completing my ReactJS app for my website, I am now ready to launch it in production mode. The only hurdle I face is getting it to work under https mode. This app was developed using create-react-app in a local environment and has since been deployed ...

Is there a way to resolve the issue of the argument being of type Boolean or undefined in React and TypeScript?

Encountering an issue, Received an error message stating: 'Argument of type 'boolean | undefined' is not assignable to parameter of 'type boolean'. Type 'undefined' is not assignable to type 'boolean'.' ...

Creating a second optional generic parameter in TypeScript

I'm having trouble with TypeScript generics My issue is as follows: I have an interface called 'Column' and a function called makeColumn to create a new column. I want to only set the first generic (UserModel), where the accessor must be a ...

TypeScript observable variable not defined

Recently, I encountered an issue and made a change to resolve it. However, I am unsure if it is the correct approach... In my project, I have defined an interface: export interface ContextEnvironment { language: string; pingUrl: string; sessionFini ...

The issue of a type error within the declaration section of a TypeScript file is causing complications in Angular or

When I attempted to run the below component, I encountered a type issue in my declaration. Here is the specific problem I am facing: Type '{ First: string[]; Second: string[]; Third: string[]; Four: string[]; }' is missing properties such as len ...