Mastering the art of constraining TypeScript function parameters using interface properties

Hey there, I've been exploring ways to restrict a function parameter so that it only accepts "strings" related to interface properties, similar to what I achieved in the validate fields function:

Please note: The TypeScript code provided here is simply for illustration purposes.

index.d.ts

export interface FieldErrors {
  errors: Array<FieldError>;
}

export type FieldsErrors<TForm> = {
  [k in keyof TForm]: FieldErrors;
}

export interface ValidateFieldsCallback<TForm> { (fieldsErrors: FieldsErrors<TForm>, values: TForm): void; };

export interface FormShape<TForm> {
  getFieldValue(fieldName: 'documentNumber' | 'userName'): void; // HOW TO FIX THIS
  validateFields(callback: ValidateFieldsCallback<TForm>): void;
  validateFields(fieldsNames: Array<string>, callback: ValidateFieldsCallback<TForm>): void;
}

example.ts

interface SignupForm {
    documentNumber: number;
    userName: string;
}

const testForm = <FormShape<SignupForm>>{};

testForm.validateFields((values) => {
    console.log(values.documentNumber); // OK
    console.log(values.userName); // OK
    console.log(values.other); // ERROR
});

// THIS SHOULD BE FIXED
const documentNumber = testForm.getFieldValue('documentNumber');

From the example above, you can see that while I have managed to define the parameter fieldsErrors in the validateFields callback, I now need to address the getFieldValue function to only accept a "valid" field name corresponding to the interface properties, and also return the appropriate type based on the interface rather than void.

I would greatly appreciate any assistance with this matter.

Answer №1

When working with mapped types, the keyof T comes into play. This type can be used in any context where a type is required and represents a union of all keys of type T, which appears to be exactly what you need.

If you want the return type of a function to match the type of a specific field, you must introduce a type parameter in the function and use a type query to fetch the type of that particular field.

export interface FormShape<TForm> {
    getFieldValue<K extends keyof TForm>(fieldName: K): TForm[K]; // Only accepts keys from TForm and returns the corresponding value of the field
    validateFields(callback: ValidateFieldsCallback<TForm>): void;
    validateFields(fieldsNames: Array<string>, callback: ValidateFieldsCallback<TForm>): void;
}

When handling arrays, there are multiple approaches available. For simple arrays, consider passing the field names as an array itself.

export interface FormShape<TForm> {
    getFieldValue<K extends keyof TForm>(...fieldName: K[]): TForm[K][] ; 
    validateFields(callback: ValidateFieldsCallback<TForm>): void;
    validateFields(fieldsNames: Array<string>, callback: ValidateFieldsCallback<TForm>): void;
}

To maintain type safety at each index, it's ideal to accept tuples as input and return tuples as well. This can be achieved through different overloads:

export interface FormShape<TForm> {
    getFieldValue<K extends keyof TForm, K1 extends keyof TForm, K2 extends keyof TForm>(fieldName: [K, K1, K2]): [TForm[K], TForm[K1], TForm[K2]]; 
    getFieldValue<K extends keyof TForm, K1 extends keyof TForm>(fieldName: [K, K1]): [TForm[K], TForm[K1]]; 
    getFieldValue<K extends keyof TForm>(fieldName: [K]): [TForm[K]]; 
    validateFields(callback: ValidateFieldsCallback<TForm>): void;
    validateFields(fieldsNames: Array<string>, callback: ValidateFieldsCallback<TForm>): void;
}

Alternatively, instead of returning an array, another approach could be to return an object containing the specified keys:

export interface FormShape<TForm> {
    getFieldValues<K extends keyof TForm>(...fieldName: K[]): { [P in K]: TForm[P] }; // Accepts only keys of TForm and returns the value of the field
    validateFields(callback: ValidateFieldsCallback<TForm>): void;
    validateFields(fieldsNames: Array<string>, callback: ValidateFieldsCallback<TForm>): void;
}

//Usage
const documentNumber = testForm.getFieldValues('documentNumber', 'userName'); //{documentNumber: number; userName: string;}

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

Applying CSS styles to a shadow DOM element will not produce the desired visual

I'm encountering an issue while attempting to apply CSS to an element within a shadow-root by adding a class to it. In my Angular component, I have the following code: CSS .test { border: 1px solid red; } TS document.getElementById('my-div&a ...

What steps can I take to ensure that the upper and left sections of a modal dialog remain accessible even when the size is reduced to the point of overflow?

One issue I'm facing is with a fixed-size modal dialog where part of the content gets cut off and becomes inaccessible when the window shrinks to cause an overflow. More specifically, when the window is narrowed horizontally, the left side is cut off ...

Is it possible to eliminate the arrows from an input type while restricting the change to a specific component?

Is there a way to remove the arrows from my input field while still applying it only to the text fields within this component? <v-text-field class="inputPrice" type="number" v-model="$data._value" @change="send ...

Displaying a React component within a StencilJS component and connecting the slot to props.children

Is there a way to embed an existing React component into a StencilJS component without the need for multiple wrapper elements and manual element manipulation? I have managed to make it work by using ReactDom.render inside the StencilJS componentDidRender ...

Typescript: The type 'T' fails to meet the requirement of being an 'object'

Ever since I installed a package along with its @types package, I've been encountering an issue with the following code: https://i.stack.imgur.com/rrRhW.png This is the error message that I'm receiving: https://i.stack.imgur.com/BfNmP.png The ...

The user's type from express-session is not being properly detected by Typescript

I have a process where I retrieve the user object from the database and set it on the express-session: export const postLogin = async ( request: Request, response: Response, next: NextFunction ): Promise<void> => { try { re ...

What are the steps to effectively troubleshoot TypeScript code in Visual Studio 2017?

Currently working on an ASP.NET Core project that heavily utilizes TypeScript. Does Visual Studio support debugging TypeScript code? ...

Create an entity with a field that holds a value type based on the value of another key field

Essentially, I am looking to create a customized "Pair" data type For example: type Pair<T extends Record<string, string | number>, K extends keyof T> = { field: K, value: T[K] } So, if we have: type Rabbit = { name: string, a ...

Encountering a glitch while integrating the angular-select2 module into an Ionic 3 application

Attempting to integrate the angular-select2 npm module into my ionic 3 app. Successfully installed the module using npm within my project Imported the package into my app.module.ts file Added <select2> tags into the html file of my application Enc ...

What is the best way to retrieve TemplateRef from an Angular component?

TS Component ngOnInit() { if(somecondition) // The line of code that is causing issues this.openModal(#tempName); } HTML Component <ng-template #tempName> Content goes here! </ng-template> this.openModal(#tempNa ...

The value returned by a mocked Jest function is ignored, while the implemented function is not invoked

Having an issue with mocking the getToken function within my fetchData method in handler.ts while working with ts-jest. I specifically want to mock the response from getToken to avoid making the axios request when testing the fetchData method. However, des ...

What is the best way to broaden the capabilities of function objects through the use of

Below is a code snippet that raises the question of how one should define certain types. In this scenario, it is required that Bar extends Foo and the return type of FooBar should be 'a'. interface Foo { (...args: any):any b: string } i ...

Experiencing migraines while integrating Firebase 9, Redux Toolkit, and Typescript in a React project. Encountering a peculiar issue where 'dispatch' is unexpectedly identified as type 'never'?

I am currently in the process of upgrading an old project to newer technologies, specifically focusing on Typescript, redux-toolkit, and Firebase v9 for modularity. While I have limited experience with Typescript and none with redux-toolkit, I have been us ...

Searching for a streamlined approach to retrieve a segment of a string

I'm currently working with JavaScript and TypeScript. Within my code, I encountered a scenario where I have a string that might contain certain tags indicating importance or urgency. Here are a couple of examples: A: "Remind me to go to the store to ...

Babel fails to substitute arrow functions

After setting up babel cli and configuring a .babelrc file with presets to es2015, I also installed the es2015 preset. However, when running the command babel script.js --out-file script-compiled.js, I noticed that arrow function syntax (=>) was still p ...

Error: Trying to access 'MaterialModule' before it has been initialized results in an uncaught ReferenceError

I have been working on a form for a personal project and attempted to implement a phone number input using this example: . However, after trying to integrate it into my project, I encountered an error. Even after removing the phone number input code, the e ...

Utilizing mapped types in a proxy solution

As I was going through the TS Handbook, I stumbled upon mapped types where there's a code snippet demonstrating how to wrap an object property into a proxy. type Proxy<T> = { get(): T; set(value: T): void; } type Proxify<T> = { ...

Hiding the line connector between data points in ChartJs

I recently took over a project that includes a line chart created using Chart.js by the previous developer. My client has requested that I do not display a line between the last two data points. Is this possible with Chart.js? I have looked through the doc ...

What is the best way to create an assertion function for validating a discriminated union type in my code?

I have a union type with discriminated properties: type Status = { tag: "Active", /* other props */ } | { tag: "Inactive", /* other props */ } Currently, I need to execute certain code only when in a specific state: // At some po ...

What are some tips for leveraging Angular input signals in Storybook?

I am currently working on setting up Storybook 8.0.8 with Angular 17.3. I have been using the Angular input() signal in my components, but I've encountered an interesting issue where the args for the storybook stories also need the argument type to be ...