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

methods for extracting JSON key values using an identifier

Is it possible to extract the Type based on both the file number and file volume number? [ { ApplicantPartySiteNumber: "60229", ManufacturerPartySiteNumber: "1095651", FileVolumeNumber: "E312534.2", Type: "Manufacturer", FileNumber ...

Attempting to establish a connection with Redis through the combination of TypeScript and Node.js

Currently, I am attempting to establish a connection with Redis using TypeScript and Node.js. However, I am encountering an issue where **error TS2693: 'RedisStore' is designated as a type but is being utilized as a value in this context.** let r ...

What are the best ways to maximize a web worker's ability to handle multiple tasks at once

I'm currently working on implementing a Web-Worker to handle its state while also managing multiple asynchronous requests. worker.ts file let a =0; //state of the worker let worker=self as unknown as Worker; worker.onmessage =(e)=>{ console ...

When utilizing typescript to develop a node module and importing it as a dependency, an issue may arise with a Duplicate identifier error (TS2300)

After creating a project called data_model with essential classes, I built a comprehensive gulpfile.js. This file not only compiles .ts to .js but also generates a unified .d.ts file named data_model.d.ts, which exports symbols and is placed at the root of ...

Strategies for iterating over an array in React with TypeScript

I'm currently working on looping through an array to display its values. Here's the code I have: ineligiblePointsTableRows() { return this.state[PointsTableType.INELIGIBLE].contracts.map(contract => { return { applied: (&l ...

Creating a personalized 404 page in your Angular Project and configuring a route for it

I am currently working on an Angular project that includes a component named 'wrongRouteComponent' for a custom 404 page. Whenever a user enters a non pre-defined route, the 'wrong-route.component.html' should be displayed. However, I a ...

Angular's implementation of a web socket connection

I am facing an issue with my Angular project where the web socket connection only opens upon page reload, and not when initially accessed. My goal is to have the socket start as soon as a user logs in, and close when they log out. Here is the custom socke ...

React component stuck in endless loop due to Intersection Observer

My goal is to track the visibility of 3 elements and update state each time one of them becomes visible. Despite trying various methods like other libraries, useMemo, useCallback, refs, etc., I still face challenges with my latest code: Endless loop scenar ...

Tips for creating an effective unit test using Jest for this specific controller

I'm currently grappling with the task of unit testing my Controller in an express app, but I seem to be stuck. Here are the code files I've been working with: // CreateUserController.ts import { Request, Response } from "express"; impor ...

Using Typescript generics to create parameter and argument flexibility for both classes and

I'm facing an issue where I need to effectively chain multiple function calls and ensure that TypeScript verifies the correctness of their linkage. export class A<T, K> { public foo(a: A<K, T>): A<K, T> { return a; } } cons ...

Update a specific form data field within an Angular application

I recently encountered a situation where I had an angular form with 9 fields and submitted it to the server using a post request. However, I realized that I had only filled in values for 8 fields while leaving one as null. Now, in a new component, I am w ...

What methods can I use to analyze the integrity of the data's structure?

Currently working on an API using NestJS and typeorm. I am in need of a way to verify the format of the data being returned to clients who make requests to it. For instance, when accessing the /players route, I expect the data to have a specific structure ...

Encountering difficulty when integrating external JavaScript libraries into Angular 5

Currently, I am integrating the community js library version of jsplumb with my Angular 5 application (Angular CLI: 1.6.1). Upon my initial build without any modifications to tsconfig.json, I encountered the following error: ERROR in src/app/jsplumb/jspl ...

After importing this variable into index.ts, how is it possible for it to possess a function named `listen`?

Running a Github repository that I stumbled upon. Regarding the line import server from './server' - how does this API recognize that the server object has a method called listen? When examining the server.ts file in the same directory, there is ...

Cypress encountered an error: Module '../../webpack.config.js' could not be located

Every time I attempt to run cypress, an error pops up once the window launches stating "Error: Cannot find module '../../webpack.config.js'" Within my plugins>index.js file, I have the following in module.exports webpackOptions: require('.. ...

How to retrieve the HTTPClient value in Angular?

APIservice.ts public fetchData(owner: any) { return this.http.get(`${this.url}/${owner}`, this.httpOptions).pipe( catchError(e => { throw new Error(e); }) ); } public fetchDataById(id: number, byId:string, owner: any) { ...

How to upload files from various input fields using Angular 7

Currently, I am working with Angular 7 and typescript and have a question regarding file uploads from multiple input fields in HTML. Here is an example of what I am trying to achieve: <input type="file" (change)="handleFileInput($event.target.files)"&g ...

Error: Unable to parse string in URI within vscode API

Console LOG displays: \Users\skhan\Library\Application Support\Code\User\summary.txt The loop is used to replace the slashes. It works fine in Windows but not in Ubuntu and Mac. This is an example on OSX 10.11.6. Howev ...

Combining multiple data types in an AJV array

Imagine you have the following defined type: type MixedArray = Array<number | string>; Now, let's say you have some sample data that needs to be validated: [ 'dfdf', 9, 0, 'sdfdsf' ] How can you create an Ajv JSONSchemeType ...

When a parameter is passed into a React-Query function with an undefined value, it can lead to the API returning a 404 error

Two parameters are sent from the frontend to trigger a GET request in another TypeScript file. It seems that one of the parameters is not successfully passed due to unknown rerenders, resulting in a 404 Error being returned by the API call in the console. ...