Improve the Precision of Return Types in Typescript by Specifying Targeted Signatures for Functions with Generic Parameters

I have the following types:

interface AutosuggestState<Item> {
  highlightedIndex: number | null;
  inputValue: string | null;
  isOpen: boolean;
  selectedItem: Item | null;
}

interface ItemToString<Item> {
  (item: Item): string;
}

interface AutosuggestProps<Item> {
    itemToString?: ItemToString<Item>;

    highlightedIndex?: number | null;
    inputValue?: string | null;
    isOpen?: boolean;
    selectedItem?: Item;

    initial?: {
        highlightedIndex?: number | null;
        inputValue?: string | null;
        isOpen?: boolean;
        selectedItem?: Item;
    }

    default?: {
        highlightedIndex?: number | null;
        inputValue?: string | null;
        isOpen?: boolean;
        selectedItem?: Item;
    }
}

This is the code:

const defaultStateValues: AutosuggestState<null> = {
  highlightedIndex: -1,
  isOpen: false,
  selectedItem: null,
  inputValue: ''
}

function getDefaultValue<
  I,
  P extends AutosuggestProps<I>,
  K extends keyof AutosuggestState<I>
>(
  props: P,
  statePropKey: K
) {
  if (props.default && typeof props.default[statePropKey] !== "undefined") {
    const ret = props.default[statePropKey];
    return ret as Exclude<typeof ret, undefined>;
  }
  return defaultStateValues[statePropKey]
}

function getInitialValue<
  I,
  P extends AutosuggestProps<I>,
  K extends keyof AutosuggestState<I>
>(
  props: P,
  statePropKey: K
) {

  if (statePropKey in props && typeof props[statePropKey] !== 'undefined') {
    const ret = props[statePropKey];
    return ret as Exclude<typeof ret, undefined>
  }

  if (props.initial && typeof props.initial[statePropKey] !== 'undefined') {
    const ret = props.initial[statePropKey];
    return ret as Exclude<typeof ret, undefined>
  }

  return getDefaultValue(props, statePropKey);
}

function getInitialState<
  I
>(
  props: AutosuggestProps<I>
): AutosuggestState<I> {

  const selectedItem = getInitialValue(props, 'selectedItem');
  const highlightedIndex = getInitialValue(props, 'highlightedIndex');
  const isOpen = getInitialValue(props, 'isOpen');
  const inputValue = getInitialValue(props, 'inputValue');

  return {
    highlightedIndex,
    isOpen,
    selectedItem,
    inputValue,
  };
}

function useAutosuggest<
  I
>(
  userProps: AutosuggestProps<I>
){
  const initialState = getInitialState(userProps);
}

Problems:

  1. If you open this code on TS Play you'll find that

The getInitialValue calls inside the getInitialState function are complaining, for example for:

const highlightedIndex = getInitialValue(props, 'highlightedIndex')

props complain – 

Argument of type 'AutosuggestProps<I>' is not assignable to parameter of type 'AutosuggestProps<unknown>'.

Similarly, the getDefaultValue call inside getInitialValue also causes complaints:

return getDefaultValue(props, statePropKey)

props complain about the same thing.

I'm unable to figure out proper types for functions getInitialValue and getDefaultValue that will incorporate the generic type I correctly, as passed from useAutosuggest. The control flow based types are challenging to write properly.

  1. The types come incorrect (in the line below) for initialState, except for selectedItem
  const initialState = getInitialState( userProps );

/**
    initialState is of type 

    highlightedIndex: number | null; ✅
    isOpen: boolean; // ✅
    selectedItem: unknown; // should be of type I | null
    inputValue: string | null; ✅
*/
  1. How do I limit the I generic in the useAutosuggest function and hence everywhere in the flow of the function call, to a particular constraint (it could either be an object or a string, nothing else)

  2. Given the code, am I wrongly typing something, or not doing something properly? Could something be typed better to make this code more efficient?

Answer №1

To set the default type, follow this format:

function getDefaultValue<
  I = any,
  P extends AutosuggestProps<I> = AutosuggestProps<I>,
  K extends keyof AutosuggestState<I> = keyof AutosuggestState<I>
>(

function getInitialValue<
  I = any,
  P extends AutosuggestProps<I> = AutosuggestProps<I>,
  K extends keyof AutosuggestState<I> = keyof AutosuggestState<I>
>(

accessible sandbox

Answer №2

After trying multiple approaches, I have discovered what I believe to be the most effective method. Prior to setting the Item as object | string, it is important to note the presence of the itemToString function within the AutosuggestProps. The function parameters must adhere to the principles of contravariance. By defining the Item as object | string, this violates those principles. Therefore, my approach involved utilizing this palyground

Sending you best wishes.

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

Validate if the program is currently running as "ionic serve" before implementing a conditional statement

Is there a method to determine if the ionic serve CLI is currently active (indicating it's not running on a physical device) within the code and use it as a condition? My problem: I have a Cordova plugin that returns a response to Cordova. When usin ...

Guide on closing a Bootstrap modal by clicking within the UI Grid in Angular

I have an Angular app written in TypeScript where I am utilizing the ui grid to present data within a bootstrap modal. My goal is to have the modal close when any of the columns in a grid row are clicked. Currently, I am not relying on $modal or $modalIn ...

Using Typescript to assign a new class instance to an object property

Recently, I crafted a Class that defines the properties of an element: export class ElementProperties { constructor( public value: string, public adminConsentRequired: boolean, public displayString?: string, public desc ...

A guide on converting your current Angular app into a "fully strict" application – follow these steps!

I started developing an Angular Application back in Angular 4 and now it has been upgraded to Angular 12. However, during the initial development phase, the strict mode was not enabled. Now that the application is stable and live in production, I am lookin ...

Error 404 occurs when attempting to retrieve a JSON file using Angular's HTTP

I'm having an issue with my service code. Here is the code snippet: import {Injectable} from '@angular/core'; import {Http, Headers, RequestOptions} from '@angular/http'; import 'rxjs/add/operator/map'; import {Client} f ...

Display captions on react-player videos using an .srt file

Currently, I am working on a React/Typescript project with Next.js. A key feature of this project is a modal that utilizes 'react-player' to display videos. While the video and modal are functioning as intended, I am looking to incorporate capti ...

Using brackets around or after an expression in Typescript

Exploring Typescript: Is there a distinction between the two square bracket notations? After running some tests, it appears they function equivalently. Any insights would be appreciated! interface test { a: string; b: string; } const x: test[] = [{a ...

Toggle the visibility of a dropdown menu based on the checkbox being checked or unchecked

One challenge I am facing involves displaying and hiding DropDown/Select fields based on the state of a Checkbox. When the checkbox is checked, the Dropdown should be visible, and when unchecked, it should hide. Below is the code snippet for this component ...

What is the proper way to define a new property for an object within an npm package?

Snippet: import * as request from 'superagent'; request .get('https://***.execute-api.eu-west-1.amazonaws.com/dev/') .proxy(this.options.proxy) Error in TypeScript: Property 'proxy' is not found on type 'Super ...

A Model in TypeScript

{ "title": { "de-DE": "German", "fr-FR": "French", "en-CA": "English" }, "image": "/tile.jpg", "url": "/url/to/version" } After receiving this JSON data, my model structure is as follows: export class MyModelStruct ...

The term 'XInterface' is not recognized in Typescript

Having some issues with my code here. I've defined a class and an interface, but Visual Studio is giving me an error saying it can't find the name 'RouteInterface'. I'm stumped as to why. import {Student} from './student&apos ...

There is a potential for the object to be 'undefined' when calling the getItem method on the window's local storage

if (window?.sessionStorage?.getItem('accessToken')?.length > 0) { this.navigateToApplication(); } Encountering the following error: Object is possibly 'undefined'.ts(2532) Any suggestions on how to resolve this issue? I am attem ...

Guide on how to update an array within typed angular reactive forms

I'm currently working on finding a solution for patching a form array in a strongly-typed reactive Angular form. I've noticed that patchValue and setValue don't consistently work as expected with FormControl. Here's an example of the fo ...

Is it possible to merge these two scripts into a single one within Vue?

As a newcomer to VUE, I am faced with the task of modifying some existing code. Here is my dilemma: Within a single component, there are two script tags present. One of them contains an import for useStore from the vuex library. The other script tag incl ...

Implementation of a recursive stream in fp-ts for paginated API with lazy evaluation

My objective involves making requests to an API for transactions and saving them to a database. The API response is paginated, so I need to read each page and save the transactions in batches. After one request/response cycle, I aim to process the data an ...

Understanding Type Inheritance in TypeScript: Assigning a variable of a parent type to a variable of a child type

A scenario involves creating two distinct types: export class GenericType { public id: string; public property: string; public name: string; } export class SpecificType extends GenericType { public value: string; } Let's say we have two vari ...

Exploring Angular2 and TypeScript integration with complex nested JSON responses

I am currently working on a project that involves a front-end app built in Angular. Upon sending a request to my back end API, I receive a nested JSON response, as shown in the attached screenshot. My query pertains to understanding how I can process this ...

Angular's implementation of a web socket connection

I am facing an issue with my Angular project where the web socket connection only opens upon page reload, and not when initially accessed. My goal is to have the socket start as soon as a user logs in, and close when they log out. Here is the custom socke ...

Error: You can't use the 'await' keyword in this context

I encountered a strange issue while using a CLI that reads the capacitor.config.ts file. Every time the CLI reads the file, it throws a "ReferenceError: await is not defined" error. Interestingly, I faced a similar error with Vite in the past but cannot ...

Angular is unable to modify a property that is marked as read-only

When attempting to update the system value in the object telecom, I encountered an error message at this stage: Cannot assign to read only property 'system' of object '[object Object]' this.organization.telecoms.forEach((telecom: T ...