Struggling with creating a generic TypeScript structure?

My goal is to manipulate data structured like this:

const exampleState  = {
  elements : {
    element1: {
      values: {
        value1: 10,
        value2: 10,
      },
      elementDetails : {
        detail1 : {
          values: {
            value1: 10,
            value2: 10,
          },
          informationDisplayed: false
        }
      }
    }
  }
}

where the columns can be dynamic parameters.

So, I defined the following types and interfaces:

type State<T extends GenericColumnList> = T & {
  elements: elementsSet<T>
}
type GenericColumnList = Record<string, number>
type elementsSet<T extends GenericColumnList> = Partial<Record<string, ElementRow<T>>>
export interface ElementRow<T extends GenericColumnList> {
  elementDetails: DetailElementsSet<T>
  values: T
}
type DetailElementsSet<T> = Partial<Record<string, ElementDetailRow<T>>>
export interface ElementDetailRow<T> {
  values: T
  informationDisplayed: boolean
}

However, when trying to pass an interface called 'ColumnList' as a parameter to the 'State' type like this:

export interface MyColumns  {
  column1?: number
  column2?: number
}

this declaration causes an error in TypeScript. For example:

const exampleState: State<MyColumns>  = {
  elements : {
    element1: {
      values: {
        value1: 10,
        value2: 10,
      },
      elementDetails : {
        detail1 : {
          values: {
            value1: 10,
            value2: 10,
          },
          informationDisplayed: false
        }
      }
    }
  }
}

The TypeScript compiler raises an error stating 'The type MyColumns does not comply with Record<string, number>' but I am unsure why this occurs.

Answer №1

The data type Record<string, number> includes a string index signature. Unfortunately, the interface MyColumns does not have an index signature, resulting in incompatible types.


An implicit index signature concept exists where types without explicit index signatures are considered compatible with indexable types as long as all known properties adhere to the index signature. However, this concept does not apply to interfaces; it only works with anonymous types (or aliases of such types). A GitHub issue, microsoft/TypeScript#15300, is currently discussing this behavior which, for now, is intentional.

To tackle this issue, consider making MyColumns an alias of an anonymous type rather than an interface:

export type MyColumns = {
  column1?: number
  column2?: number
}

In this scenario, you must include undefined in the domain of GenericColumnList since the type of MyColumns['column1'] is number | undefined, not just number:

type GenericColumnList = Record<string, number | undefined>

With these adjustments, your assignment will be successful:

const stateTest: State<MyColumns> = {
  natures: {
    nature1: {
      amounts: {
        column1: 10,
        column2: 10,
      },
      natureDetails: {
        detail1: {
          amounts: {
            column1: 10,
            column2: 10,
          },
          descriptionShown: false
        }
      }
    }
  }
}; // okay

To avoid mandating the use of non-interface types for T, you can make GenericColumnList generic in T itself so that instead of an index signature, it contains the same keys as T:

type GenericColumnList<T> = { [K in keyof T]?: number } 

In this case, the properties are optional (?) to cater to missing or optional keys especially when using --strictNullChecks.

This approach requires some <T> additions in your code:

type State<T extends GenericColumnList<T>> = T & {
  natures: NaturesSet<T>
}
type NaturesSet<T extends GenericColumnList<T>> = Partial<Record<string, RowNature<T>>>
export interface RowNature<T extends GenericColumnList<T>> {
  natureDetails: NatureDetailsSet<T>
  amounts: T
}

Once again, your assignment will succeed:

const stateTest: State<MyColumns> = {
  natures: {
    nature1: {
      amounts: {
        column1: 10,
        column2: 10,
      },
      natureDetails: {
        detail1: {
          amounts: {
            column1: 10,
            column2: 10,
          },
          descriptionShown: false
        }
      }
    }
  }
}; // okay

Link to playground code

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

Leveraging property information for a dropdown field

In my index.tsx file, I am fetching data that I pass to the component FormOne. This allows me to access the data in FormOne using props.data const [data, setData] = useState([]); ... return ( ... <FormOne data={data} /> ); I am looking to ...

Sharing the input value with a service in Angular 4

I am a beginner when it comes to Angular 4. I currently have a variable named "md_id" which is connected to the view in the following way. HTML: <tr *ngFor="let item of driverData"> <td class="align-ri ...

Should one bother utilizing Promise.all within the context of a TypeORM transaction?

Using TypeORM to perform two operations in a single transaction with no specified order. Will utilizing Promise.all result in faster processing, or do the commands wait internally regardless? Is there any discernible difference in efficiency between the t ...

Gain insights on Stripe Webhooks with Firebase Functions and Node.js

I've been struggling to integrate Firebase functions with Stripe webhooks for listening to events. Take a look at my code: exports.stripeEvents = functions.https.onRequest((request, response) => { try { const stripeSignature = request. ...

Every time an action is carried out in the app, React generates countless TypeError messages

Whenever I'm using the application (particularly when started with npm start), my console gets flooded with thousands of TypeError messages like this: This issue doesn't occur when I build the app... It's frustrating navigating through the ...

Troubleshooting problem with @Input number in Angular 2

Here is the component in question: <component value="3"></component> This is the code for the component: private _value:number; get value(): number { return this._value; } @Input() set value(value: number) { console.log(v ...

Execute various Office Scripts functions within a single script based on the button that is selected

Imagine you have an Excel spreadsheet with two buttons named populate-current and populate-all. Both buttons execute the same Office Script function that looks something like this: function populateByRowIndex(workbook: ExcelScript.Workbook, rowIndex: numbe ...

State does not contain the specified property - Navigating with React Router Hooks in TypeScript

I've been experiencing issues when using react-router-dom hooks with TypeScript. Whenever I try to access a state property, I get an error stating that it doesn't exist. For more information, you can visit: To continue development, one workaro ...

Top tips for handling HTML data in JSON format

I'm looking to fetch HTML content via JSON and I'm wondering if my current method is the most efficient... Here's a sample of what I'm doing: jsonRequest = [ { "id": "123", "template": '<div class=\"container\"&g ...

Component coding in Angular 2 allows for seamless integration and customization of Material

I am looking to initiate the start.toggle() function (associated with Angular 2 material md-sidenav-layout component) when the test() method is triggered. How can I execute md-sidenav-layout's start.toggle() in the app.component.ts file? app.componen ...

The parameter in the Typescript function is not compatible with the generic type

What causes func1 to behave as expected while func2 results in an error? type AnyObj = Record<string, any>; type Data = { a: number; b: string }; type DataFunction = (arg: AnyObj) => any; const func1: DataFunction = () => {}; const arg1: Data ...

Can a "fragile export" be generated in TypeScript?

Testing modular code can be challenging when you have to export things just for the purpose of testing, which can clutter your code and diminish the effectiveness of features like "unused variable" flags on compilers or linters. Even if you remove a usage ...

Efficient access to variable-enumerated objects in TypeScript

Let's say I have the following basic interfaces in my project: interface X {}; interface Y {}; interface Data { x: X[]; y: Y[]; } And also this function: function fetchData<S extends keyof Data>(type: S): Data[S] { return data[type]; } ...

Refresh a page automatically upon pressing the back button in Angular

I am currently working on an Angular 8 application with over 100 pages (components) that is specifically designed for the Chrome browser. However, I have encountered an issue where the CSS randomly gets distorted when I click the browser's back button ...

No data is generated when choosing from the dropdown menu in mat-select

I have a select function where all options are selected, but the selected sections are not shown. When I remove the all select function, everything works fine. Can you help me find the error? Check out my work on Stackblitz Here is my code: Select <m ...

Unusual behavior when importing in Angular 2 using TypeScript

While working on a demo for another question on Stack Overflow, I initially used angular-cli and then switched to Plunker. I noticed a peculiar difference in behavior with the import statement between the two setups. The issue arises with the second impo ...

What is the method to remove curly brackets from a different data category?

If I have a type like this type Z = {a: number} | {} | {b: boolean} | {c: string} | ...; Is there a way to get the same type but without {}? type Y = Exclude<Z, {}>; ⇧This will result in Y = never, because all variants can be assigned to {} and a ...

Utilizing custom hooks for passing props in React Typescript

I have created a unique useToggler custom hook, and I am attempting to pass toggle props from it to the button child in the Header component. However, when I try using toggle={toggle}, I encounter this error: Type '{toggle: () => void;}' is ...

Removing items with properties that are null or undefined

My current situation involves using the AWS SDK, and I've noticed that many of its objects have members that can be undefined. Take for example S3.Object: export interface Object { /** * */ Key?: ObjectKey; /** * * ...

Executing the Angular 2 foreach loop before the array is modified by another function

Currently, I am facing some difficulties with an array that requires alteration and re-use within a foreach loop. Below is a snippet of the code: this.selectedDepartementen.forEach(element => { this.DepID = element.ID; if (this.USERSDepIDs. ...