Modify interface property type based on another property

Currently, I am tackling a grid project in React and have come across specific types and interfaces (view code here):

export type DataGridColumnType = 'currency' | 'date' | 'number' | 'text';

interface CurrencyColumnOptions {
  symbol: string;
}

interface DateColumnOptions {
  format: string;
}

interface NumberColumnOptions {
  decimalPoint: string;
  thousandSeparator: string;
}

type TextColumnOptions = {};

export interface DataGridColumn {
  field: string;
  label: string;
  title: string;
  columnOptions?: {}; // either CurrencyColumnOptions, DateColumnOptions, NumberColumnOptions or TextColumnOptions based on the "type" property
  type?: DataGridColumnType;
}

export interface DataGrid {
  columns: DataGridColumn[];
  rows: Record<string, unknown>[];
}

const column: DataGridColumn = {
  field: 'id',
  label: 'ID',
  title: 'ID',
  type: 'date',
  columnOptions: {
    format: 'YYYY-mm-dd',
  },
};

My objective is to switch the type of the "columnOptions" property in "DataGridColumn" based on the value of "type". For instance, when "type" is "date", I want to utilize DateColumnOptions for the "columnOptions" property.

I understand that utilizing a generic could be a solution, but these interfaces are buried deep within my grid system, and introducing a generic might complicate the grid usage.

Researching this issue has not yielded definitive results. Can this be achieved, or must I create multiple interfaces and apply union types?

Your insights would be greatly appreciated.

Answer №1

When you need to restrict two or more properties of an object type in a specific way, you can achieve this by either creating a union of acceptable configurations for that object type or using a generic type where the constrained properties are dependent on the generic type parameter.

In the given scenario, the type property is a string literal that determines the type of the columnOptions property. This situation is a perfect fit for a discriminated union, with type acting as the discriminant.


Keep in mind that an interface cannot be a union type; if you want DataGridColumn to be a discriminated union, you will have to use a type alias instead. While this change is acceptable, there are some key differences between type aliases and interfaces to consider.

Instead of manually enumerating all configurations of DataGridColumn, you can refactor the code and utilize type manipulation techniques to define DataGridColumn without redundancy:

// Refactored approach
type DataGridColumnType = keyof DataGridColumnOptions;

type DataGridColumn = { [K in DataGridColumnType]: BaseDataGridColumn & {
  type: K,
  columnOptions?: DataGridColumnOptions[K]
}}[DataGridColumnType]

By following this refactored approach, you can define DataGridColumn in terms of non-redundant types and leverage type mapping to achieve the desired discriminated union.

If this solution aligns with your requirements, great! There are further optimizations and adjustments that can be made, but those can be explored as needed.

Answer №2

It may not be exactly what you had in mind, but it could be helpful.

One way to declare a variable is by using the typeof keyword.

let variable1: string = 'asd';
let variable2: typeof variable1; // variable2 is a string

For more information, check out https://www.typescriptlang.org/docs/handbook/2/typeof-types.html

Answer №3

To create a versatile interface, consider implementing IColumnType to house shared data among various types. By doing so, you enable the flexibility to assign any type to columnOptions as long as it adheres to the IColumnType interface.

Alternatively

Another approach is to utilize generic programming. Here is an example:

export interface DataGridColumn<T>{
  field: string;
  label: string;
  title: string;
  columnOptions?: T;
  component?: React.ReactNode;
  type?: DataGridColumnType;
}

Implement it as follows:

let dgc = new {
  ...
} as DataGridColumn<DataGridColumnType>;

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

Having trouble getting the styles property to work in the component metadata in Angular 2?

Exploring Angular 2 and working on a demo app where I'm trying to apply the styles property within the component metadata to customize all labels in contact.component.html. I attempted to implement styles: ['label { font-weight: bold;color:red } ...

Issues arise in TypeScript 5.1.3 with lodash due to type errors involving excessively deep type instantiation, which may potentially be infinite

Recently, I made updates to my Ionic/Angular project and now it is running Angular version 16.1.3 along with TypeScript version 5.1.3. In addition to this, my project also includes the following dependencies: "lodash-es": "^4.17.21", ...

What is the process for including a new attribute to the Component in the _app.tsx file when using Next.js along with Typescript?

Currently, I am in the process of developing some secure pages using Next.js, NextAuth.js, and Typescript. While referencing code from my _app.tsx, which was sourced from the official NextAuth.js website, I encountered an issue where a property named auth ...

What is the process of creating an asynchronous function that will resolve a promise when the readline on('close') event is triggered within it in Typescript?

Here's a code snippet I'm working with: private readFile() { var innerPackageMap = new Map<string, DescriptorModel>(); // Start reading file. let rl = readline.createInterface({ input: fs.createReadStream(MY_INPUT_FILE ...

When a property is designated as readonly in a TypeScript class, it can still be modified despite its intended

I'm currently grappling with the concept of the readonly keyword in TypeScript. As far as I understand, a readonly property should not be able to be written to after the constructor is called. However, in my testing, I've found that I can actuall ...

What is the process for importing a map from an external JSON file?

I have a JSON file with the following configuration data: { "config1": { //this is like a map "a": [ "string1", "string2"], "b": [ "string1", "string2"] } } Previously, before transitioning to TypeScript, the code below worked: import ...

Choose a specific div element from a collection of dynamically generated divs in Angular

I have been working on a code to dynamically generate div elements using the *ngFor directive. Here is what I have so far: <div *ngFor = "let item of Items"> <p>Item : {{item}} </p> </div> The challenge I encountered is that w ...

Instructions on how to present a list of employee information according to the user's gender preference using a selection of three radio buttons

I have developed a view that displays a table of employees, using a json array to store their details in the component. Additionally, I have implemented 3 radio buttons: all, male, and female. My goal is to have the table show all employees when "all" is ...

The 'Set-Cookie' response header failed to be recognized for a subsequent HTTP request

When a user successfully logs in, their information is stored in a cookie using the "Set-Cookie" response header. However, I am facing an issue where the cookie seems to get lost when making subsequent requests from the client. As a result, the server trea ...

A guide to utilizing the spread operator within a typescript tuple

Is it possible to set the structure of an array without knowing the exact number of elements it will contain? How can I achieve this flexibility in defining array configurations? (Check out a playground version here): type numStrArr = [number, ...string]; ...

I'm diving into the world of Typescript and trying to figure out how to use tooltips for my d3 stacked bar chart. Any guidance on implementing mouseover effects in Typescript would be greatly

I am currently facing some issues with the code below and need guidance on how to proceed. I am new to this and unsure of how to call createtooltip. Any assistance would be greatly appreciated. The error message states that createtooltip is declared but n ...

Angular failing to retrieve URL parameters correctly

As I was trying to retrieve URL queries like www.website.com?a:b, I decided to follow the guidance provided in a particular Angular tutorial. This official tutorial (accessible via this link) instructed me to implement the following simple code snippet wit ...

Issue with type narrowing and `Extract` helper unexpectedly causing type error in a generic type interaction

I can't seem to figure out the issue at hand. There is a straightforward tagged union in my code: type MyUnion = | { tag: "Foo"; field: string; } | { tag: "Bar"; } | null; Now, there's this generic function tha ...

What is an example of an array attribute within a generic class?

In my typescript code, I have created a generic class with two properties like this - export class WrapperModel<T>{ constructor(private testType: new () => T) { this.getNew(); } getNew(): T { return new this.testType ...

Angular 2 does not recognize the existence of .then in type void

I have a query regarding Angular2 and I'm struggling with the void function in my code. Can someone help me out? I am new to Angular2 and unsure of what needs to be added in the void function. Check out this image for reference export class PasswordR ...

The Typescript hello world example encounters an issue with Karma

Recently, I encountered an issue while working on a TypeScript project with Jasmine and Karma. It seems that Karma is unable to execute the compiled unit tests due to an error showing up in Chrome: Uncaught ReferenceError: define is not defined To illust ...

Dynamically incorporating validation to an ngModel input

Utilizing NgForm, I dynamically added a validator to the input field. In my first example, everything works perfectly when I use the button setValidation to validate the input. However, in the second example where I add formGroup, I encounter an error whe ...

Could the selection determine if a value is exported?

Need help with updating a Typescript code snippet: export const defaultListingFormValues = { itemWeight: 1 } Is it possible to dynamically adjust the default value of itemWeight based on a selected category in a dropdown menu for a listing form? For ex ...

Utilizing external imports in webpack (dynamic importing at runtime)

This is a unique thought that crossed my mind today, and after not finding much information on it, I decided to share some unusual cases and how I personally resolved them. If you have a better solution, please feel free to comment, but in the meantime, th ...

Unable to utilize the namespace 'RouteComponentProps' as a specified type

When using TypeScript to define an interface that extends RouteComponentProp, I encountered some issues: VSCode error: [ts] Cannot use namespace "RouteComponentProps" as a type. Console error: Cannot use namespace 'RouteComponentProps' as a typ ...