Determining TypeScript function attributes through inference

I am working on creating a customized form generator that is strongly typed, and I need to configure it with options based on the model. I'm wondering if it's possible to determine the return type of a function from its arguments in TypeScript, as I may have overlooked some inference basics.

To illustrate my point, I have created a simple, synthetic (and somewhat pointless) example:

// Defining an Option type that takes an OptionName key and returns fields based on the Model 

type Options<Model extends Record<string, any>, OptionName extends string = string> = {
  [key in OptionName]: {
    fields: {
      [field in keyof Model]?: {
        label: string;
      };
    };
  };
};


// Here is a basic function that returns options and expects the return type to be based on the provided options

function useOptions<Model extends Record<string, any>>(options: Options<Model>): Options<Model, keyof typeof options> {
  return options as Options<Model, keyof typeof options>;
}

Here is how you can use this function:

type Person = {
  lastname: string;
  firstname: string;
  country: string;
};

const options: Options<Person> = {
  ContactFields: {
    fields: {
      lastname: {
        label: 'Lastname',
      },
      firstname: {
        label: 'Firstname',
      },
    },
  },
  AddressFields: {
    fields: {
      country: {
        label: 'Country',
      },
    },
  },
};


// No type error is returned 
const { ContactFields, AddressFields } = useOptions<Person>(options);

// No type error is returned
// Should ideally return type errors since the return type doesn't match the options
const { OptionOne, OptionTwo } = useOptions<Person>(options);

The expected return type should look like this:

type ExpectedReturnType = {
  ContactFields: {
    fields: {
      [key in keyof Person]?: {
        label: string;
      };
    };
  };
  AddressFields: {
    fields: {
      [key in keyof Person]?: {
        label: string;
      };
    };
  };
};

Is there a way in TypeScript to solve this issue or should I consider an alternative approach?

Thank you in advance for your insights :)

Answer №1

Consider this scenario:

// Defining an Option type that takes keys as OptionName and returns fields based on Model 

type Options<Model extends Record<string, any>, OptionName extends string = string> = {
    [key in OptionName]: {
        fields: Partial<Record<keyof Model, { label: string }>>
    };
};


// Implementing a basic function that returns options and expects the return type to be based on the given options

function useOptions<Keys extends string, Model extends Record<string, any>,>(options: Options<Model, Keys>): Options<Model, Keys> {
    return options as Options<Model, keyof typeof options>;
}

type Person = {
    lastname: string;
    firstname: string;
    country: string;
};

const options = {
    ContactFields: {
        fields: {
            lastname: {
                label: 'Lastname',
            },
            firstname: {
                label: 'Firstname',
            },
        },
    },

};


const result = useOptions(options)

Playground

If you want to infer the return type, you need to infer the input type.

To infer the input type, remove explicit types from const options and the explicit generic parameter in useOptions<Person>.

If you are interested in argument inference in functions, check out my article

P.S. I added a Keys generic argument to infer the root keys of the input parameter


Avoid using explicit generic parameters when calling your function. There are only two justified cases when it is allowed.

First When dealing with an empty array:

const handleArray = <T,>(arr: T[]) => arr

handleArray([]) // never []
handleArray<number>([]) // number[]

Second When making an API call:

const handleApi = <T,>() => ({}) as T

handleApi<{ user: string }>() // {user: string}

Try to infer your input parameters instead of explicitly declaring them

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

Exploring the relationships between nested tuple types

When exploring the concept of mapped tuple types in TypeScript, the documentation provides an example: type MapToPromise<T> = { [K in keyof T]: Promise<T[K]> }; type Coordinate = [number, number] type PromiseCoordinate = MapToPromise<Coor ...

Tips for modifying JSON response using a function

When I call the function buildFileTree, I store its response in a constant variable called data. const data = this.buildFileTree(dataObject, 0); The value of dataObject is: const dataObject = JSON.parse(TREE_DATA); And the content of TREE_DATA is: cons ...

TypeScript fails to detect errors in setting state with incorrect interface properties in React components

Even though I clearly defined an interface with specific props and assigned that interface to be used in useState, no error is triggered when the state value is set to an array of objects with incompatible props: This is how ResultProps is defined: interf ...

Issues with Angular 9 application compatibility with IE11

Our Angular 9 project runs smoothly on Google Chrome and Firefox, but nothing appears on IE11. Despite trying various solutions found online and following related blogs, the issue remains unresolved. Here is a snippet from my tsconfig.json: { // Com ...

Angular 13 implementation of a double-loop structure for fetching data from an API

I'm facing an issue with retrieving specific data fields label and svm from a JSON file. The desired fields are nested inside PORTFOLIO > REGROUPEMENT > ELEMENT. You can access the JSON file here. img(1) I've attempted to display the dat ...

Cypress has the ability to exclude certain elements from its testing

How do I locate a clickable element using the cypress tool? The clickable element always contains the text "Login" and is nested inside the container div. The challenge lies in not knowing whether it's an <button>, <a>, or <input type=& ...

Mongoose encountered an error when attempting to cast the value "ObjectID" to an ObjectId at the specified path "red.s1"

My Mongoose schema is structured as follows: const gameSchema = new Schema({ matchNumber: { type: Number, required: [true, 'A match must have a number!'], unique: true }, red: { s1: { type: ...

Is Angular File API Support Compatible with HTML5?

When checking for HTML5 File API browser support in my code: private hasHtml5FileApiSupport; constructor(@Optional() @Inject(DOCUMENT) document: Document) { const w = document.defaultView; this.hasHtml5FileApiSupport = w.File && w.FileReader & ...

I'm on the lookout for a component similar to angular-ui-tree that is compatible with angular

As a new developer, I am in search of a component similar to: But specifically for Angular 6, with all the same functionality (drag-and-drop capability, nested items, JSON structure, etc.). I have come across some components that either lack dragging fun ...

The optimization efforts on several components ended up having a negative impact on the overall performance

I've been dedicated to optimizing an online game app recently. This React app has a large code base and is experiencing some major lag issues, especially on the mobile version. Throughout this process, I've come across various challenges includi ...

Is it possible to store any array of strings in localStorage?

I'm trying to save a list of addresses in local storage, like this: addresses["1 doe st, england","2 doe st, england", "3 doe st, england"] Each time I click on an address, I want it to be added to the array in local storage. However, my current imp ...

Custom options titled MUI Palette - The property 'primary' is not found in the 'TypeBackground' type

I am currently working on expanding the MUI palette to include my own custom properties. Here is the code I have been using: declare module '@mui/material/styles' { interface Palette { border: Palette['primary'] background: Pa ...

Angular - Customizing button bindings for various functions

As a newcomer to Angular and TypeScript, I am faced with the task of creating a customizable button that can display text, an icon, or both. For example: button-icon-text-component.html <button> TEST BUTTON </button> app.component.html & ...

Automatically divide the interface into essential components and additional features

Consider the following interfaces: interface ButtonProps { text: string; } interface DescriptiveButtonProps extends ButtonProps { visible: boolean, description: string; } Now, let's say we want to render a DescriptiveButton that utilize ...

Issue encountered while trying to iterate through an observable: Object does not have the capability to utilize the 'forEach' property or method

I am currently following the pattern outlined in the hero.service.ts file, which can be found at this link: https://angular.io/docs/ts/latest/guide/server-communication.html The Observable documentation I referenced is available here: When examining my c ...

Is there a way to seamlessly share TypeScript types between my Node.js/Express server and Vite-React frontend during deployment?

I'm currently tackling a project that involves a Node.js/Express backend and a Vite-React frontend. My goal is to efficiently share TypeScript types between the two. How should I configure my project and build process to achieve this seamless type sha ...

Are undefined Static Properties an Issue in Mocked Classes? (Jest)

Currently, I am facing a challenge in mocking a class that includes a static property. jest.mock("../../src/logger/index"); import { Logger } from "../../src/logger/index"; // .. const LoggerMock = Logger as jest.MockedClass<typeof ...

How to update an Array<Object> State in ReactJS without causing mutation

In my program, I store an array of objects containing meta information. This is the format for each object. this.state.slotData [{ availability: boolean, id: number, car: { RegistrationNumber : string, ...

Using jest to simulate a private variable in your code

I am working on unit testing a function that looks like this: export class newClass { private service: ServiceToMock; constructor () { this.service = new ServiceToMock() } callToTest () { this.service.externalCall().then(()=& ...

Choose a single entity from the ngrx store and display it in a separate template

I have been working on a blog application that utilizes ngrx for state management. Currently, there are two main objects stored in the application: { "posts": [...], "users": [...] } Now, I am looking to create a separate component spe ...