5

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;
   }
}

3 Answers 3

4

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.

In a comment, captain-yossarian pointed out a couple of relevant issues on the TypeScript issue list:

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

Sign up to request clarification or add additional context in comments.

2 Comments

@captain-yossarian - Oh, interesting! Thanks!
1

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;
}

3 Comments

This also works, however this could get a little confusing if there's not really a relation between Item and Task.
You're definitely right, i forgot to add the generic type. Anyway he told that both of those items have at least id and count properties so i guessed that id and count could have been placed in the base interface
Thanks a lot. As a matter of fact: a Task ist just a variant of an Item (or the other way round). Since both are records from the same table, you could even say they are the same.
1

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.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.