Tips for fixing type declaration in a generic interface

Here is a simple function that constructs a tree structure.

interface CommonItem {
  id: string
  parent: string | null
}

interface CommonTreeItem {
  children: CommonTreeItem[]
}

export const generateTree = <Item extends CommonItem, TreeItem extends CommonTreeItem & Item>(
  data: Item[]
): TreeItem[] => {
  const root: TreeItem[] = []
  const dataMap = new Map<string, TreeItem>()

  for (const item of data) {
    dataMap.set(item.id, {...item, children: []})
    //                   ~~~~~~~~~~~~~~~~~~~~~~~~
    //                   TS2345: Argument of type 'Item & { children: never[]; }' is not assignable to parameter of type 'TreeItem'.
  }

  for (const originalItem of data) {
    if (originalItem.parent) {
      const parentItem = dataMap.get(originalItem.parent)
      const item = <TreeItem>dataMap.get(originalItem.id)
      if (parentItem) {
        parentItem.children.push(item)
      }
    } else {
      root.push(
        <TreeItem>dataMap.get(originalItem.id)
      )
    }
  }

  return root
}

The linter is throwing errors and I'm unsure why. I attempted to create generic types with Item but it didn't work out. Perhaps there's a need to construct TreeItem in a different manner!?

Answer №1

The error message that I am encountering is as follows:

Argument of type 'Item & { children: never[]; }' is not compatible with parameter of type 'TreeItem'.
  'Item & { children: never[]; }' can be assigned to the constraint of type 'TreeItem', but it is possible for 'TreeItem' to be instantiated with a different subtype of constraint 'CommonTreeItem & CommonItem'.ts(2345)

This issue arises from how

TreeItem extends CommonTreeItem & Item
only specifies that the resulting type should include id, parent, and children, allowing for additional fields. The type checking on Map#set is stricter, demanding an exact match for the item being set (no extra fields allowed).

To overcome this error, one can define TreeItem more precisely instead of using extends in the following manner:

const buildTree = <Item extends CommonItem>(data: Item[]) => {
  type TreeItem = CommonTreeItem & Item
  const root: TreeItem[] = []
  const dataMap = new Map<string, TreeItem>()

  for (const item of data) {
    dataMap.set(item.id, { ...item, children: [] })
  }

  for (const originalItem of data) {
    if (originalItem.parent) {
      const parentItem = dataMap.get(originalItem.parent)
      const item = <TreeItem>dataMap.get(originalItem.id)
      if (parentItem) {
        parentItem.children.push(item)
      }
    } else {
      root.push(<TreeItem>dataMap.get(originalItem.id))
    }
  }

  return root
}

The return type annotation has been removed, but the function's type should be inferred correctly as

const buildTree: <Item extends CommonItem>(data: Item[]) => TreeItem[]

Additional Resources:

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

The sequence of execution in React hooks with Typescript

I'm having difficulty implementing a language switching feature. On the home page of my app located at /, it should retrieve a previously set preference from localStorage called 'preferredLanguage'. If no preference is found, it should defau ...

The 'connectedCallback' property is not found in the 'HTMLElement' type

After taking a break from my project for a year, I came back to find that certain code which used to work is now causing issues: interface HTMLElement { attributeChangedCallback(attributeName: string, oldValue: string, newValue: string): void; con ...

The variable 'React' is defined but not utilized in the code

Here's the code snippet in question: // tslint:disable import * as React from 'react'; import { Input, InputProps } from '../atoms/Input/Input'; import { FormControl } from '../hoc/FormControl/FormControl'; export const ...

I'm curious as to why I am receiving an empty array as a response when I send a post request to the API

I am experiencing an issue while sending data to an API using a post method. I have structured the data in an object as per the server's requirements, but upon posting the object to the API, I receive a response with a status of 200 and an empty array ...

The specified type 'Observable<{}' cannot be assigned to the type 'Observable<HttpEvent<any>>'

After successfully migrating from angular 4 to angular 5, I encountered an error in my interceptor related to token refreshing. The code snippet below showcases how I intercept all requests and handle token refreshing upon receiving a 401 error: import { ...

The Generic Function's Return Type in Typescript

The latest addition of ReturnType in TypeScript 2.8 is a highly valuable feature that enables you to capture the return type of a specific function. function foo(e: number): number { return e; } type fooReturn = ReturnType<typeof foo>; // numbe ...

Why does the custom method only trigger once with the addEventListener?

I am attempting to connect the "oninput" event of an input range element to a custom method defined in a corresponding typescript file. Here is the HTML element: <input type="range" id='motivation-grade' value="3" min="1" max="5"> This i ...

Testing the branch count of optional chaining in Typescript

I am struggling to grasp the concept of branch coverage, especially when it involves optional chaining in TypeScript. Below is my code snippet: type testingType = { b?: { a?: number }; }; export function example(input: testingType) { return input. ...

Angular users should be cautious of the 'grid zero width' warning that may arise when employing ag-Grid's sizeColumnsToFit() on multiple ag-Grids simultaneously

I'm encountering an issue with ag-grid where I see the following warning in the console. Despite conducting some research, none of the solutions I found have resolved my problem. It appears that there may be a memory leak within my application based o ...

Examining the function of a playwright script for testing the capability of downloading files using the window.open

Currently, we are working on a project that uses Vue3 for the frontend and we are writing tests for the application using Playwright. Within our components, there is a download icon that, when clicked, triggers a handler to retrieve a presigned URL from S3 ...

Tracking code execution in React, Enzyme, and Istanbul reveals uncovered functions running during tests

I have been working on testing a React component that involves 3 functions. The tests I've written for these functions pass successfully, but my code coverage report indicates only a 33% coverage. Here is the code snippet of the component: const AddW ...

Zero's JSON Journey

When I make an HTTP request to a JSON server and store the value in a variable, using console.log() displays all the information from the JSON. However, when I try to use interpolation to display this information in the template, it throws the following er ...

Developing Angular 7 Forms with Conditional Group Requirements

I am currently in the process of developing a form that enables users to configure their payment preferences. The form includes a dropdown menu where users can select their preferred payment method. For each payment method, there is a FormGroup that co ...

Extract and preserve elements from an ordered array by segregating them into separate arrays of objects using Angular 8

I have an array called arrayReceived containing 15 objects. My goal is to sort and store the first 6 objects with the lowest amount value in a new array called arraySorted. These objects are sorted based on their amount parameter. There may be multiple obj ...

When defining a GraphQL Object type in NestJS, an error was encountered: "The schema must have unique type names, but there are multiple types named 'Address'."

Utilizing Nestjs and GraphQL for backend development, encountered an error when defining a model class (code first): Schema must contain uniquely named types but contains multiple types named "Address". Below is the Reader model file example: @ObjectType() ...

How can I nest a kendo-grid within another kendo-grid and make them both editable with on-cell click functionality?

I am facing an issue with my 2 components - trial1 (parent kendo-grid) and trial2 (child kendo-grid). Inside the template of trial1, I referenced the sub-grid component trial2. However, I am encountering an error where trial2 is not recognized inside trial ...

Do these two syntaxes for fat arrow functions differ in any way, or are they essentially the same in function?

I've noticed in Angular 6 / Typescript code examples that fat arrow functions are used with two different syntaxes. Is there any distinction between them, or do they perform the same functionally? blah.then(param => { // perform some action wi ...

Asynchronous handling of lifecycle hooks in TypeScript for Angular and Ionic applications

I'm intrigued by the idea of integrating TypeScript's async/await feature with lifecycle hooks. While this feature is undeniably convenient, I find myself wondering if it's considered acceptable to make lifecycle hooks asynchronous. After ...

The current enablement status does not support the experimental syntax 'flow' (7:8):

Utilizing a Mono repo to share react native components with a react app has presented some challenges. When attempting to use a react native component from react, an error keeps popping up that I can't seem to resolve. I've attempted to follow t ...

Extract from Document File

After receiving a PDF through an Angular Http request from an external API with Content Type: application/pdf, I need to convert it into a Blob object. However, the conventional methods like let blobFile = new Blob(result) or let blobFile = new Blob([resul ...