Home > Software engineering >  Typescript: How to apply the same function to different types of arrays
Typescript: How to apply the same function to different types of arrays

Time:12-01

This typescript function shoud be applicable to two different types of arrays (i.e. arrays with different interfaces) which both have two properties (id and count) in common. The function updates the count property of an item OR task with a given id. But I get a typescript error at the "find" command: "The expression is not callable." If I remove one of the Interfaces, i.e. if I only define the input parameter "items" as a Item-array (Item[]) OR an Task-array (Task[]) it works. But I want to apply the function to both types of arrays.

//interface definitions
export interface Item {
    id: number,
    count: number,
    other: number
}
export interface Task {
    id: number,
    count: number,
    more: string
}

//function
function UpdateCount(items: Item[] | Task[], id: number, count: number) {
   let item = items.find(obj => obj.id === id);
   if (item !== undefined) {
      item.count = count;
   }
}

CodePudding user response:

The details in the error message are useful:

Each member of the union type...has signatures, but none of those signatures are compatible with each other.

What it's saying is that this function signature:

find((obj: Item, index: number, array: Item[]): Item | undefined

and this function signature:

find((obj: Task, index: number, array: Task[]): Task | undefined

are incompatible.

You can solve it in a couple of ways. Probably the simplest is to define an interface that Item and Task can both extend:

//interface definitions
interface Base {
    id: number,
    count: number,
}
export interface Item extends Base {
    other: number
}
export interface Task extends Base {
    more: string
}

Then the function can either just accept Base, or use a generic parameter that extends Base:

function UpdateCount<ElementType extends Base>(items: ElementType[], id: number, count: number) {
   let item = items.find(obj => obj.id === id);
   if (item !== undefined) {
      item.count = count;
   }
}

You don't have to define the interface separately, you could just use {id: number, count: number} instead of Base in that function, but then if you change that common aspect of Item or Task, your function won't match them anymore.

Playground link

CodePudding user response:

You could consider using a generic function.

//interface definitions
export interface Item {
    id: number,
    count: number,
    other: number
}
export interface Task {
    id: number,
    count: number,
    more: string
}

//function
function UpdateCount<K extends { id: number, count: number }>(
  items: K[],
  id: number,
  count: number
) {
   let item = items.find(obj => obj.id === id);
   if (item !== undefined) {
      item.count = count;
   }
}

What that is basically saying is that Items will be an array of some type which includes an id and a count property. So long as what you pass into that function has those two properties, it should be good.

CodePudding user response:

My solution would be:

//interface definitions
interface BaseItem {
    id: number,
    count: number
}

export interface Item extends BaseItem {
    other: number
}
export interface Task extends BaseItem {
    more: string
}

//function
function UpdateCount<T extends BaseItem>(items: T[], id: number, count: number) {
   const item = items.find(i => i.id === id);
   if (!item) return; 
   item.count = count;
}
  • Related