skip to Main Content

Learning typescript. Here is the code:

interface TodoI {
    id: number;
    title: string;
    status: string;
    completedOn?: Date;
}

const todoItems: TodoI[] = [
    { id: 1, title: "Learn HTML", status: "done", completedOn: new Date("2021-09-11") },
    { id: 2, title: "Learn TypeScript", status: "in-progress" },
    { id: 3, title: "Write the best app in the world", status: "todo", },
]

function addTodoItem(todo: string): TodoI {
    const id = getNextId<TodoI>(todoItems)

    const newTodo = {
        id,
        title: todo,
        status: "todo",
    }

    todoItems.push(newTodo)

    return newTodo
}

function getNextId<T>(items: T[]) : number {
    // following line is causing the issue
    // Property 'id' does not exist on type 'T'.ts(2339)
    return items.reduce((max, x) => x.id > max ? x.id : max, 0) + 1
}

const newTodo = addTodoItem("Buy lots of stuff with all the money we make from the app")

console.log(JSON.stringify(newTodo))

While calling the getNextId() function I’ve specified type type TodoI, then why I am supposed to extend the generic type?

2

Answers


  1. The problem here is not with the function call.

    So if you remove the function call but not the definition you should in theory get the same error (assuming this error comes up before the tree-shaking phase in the bundling process).

    Now to answer you question about extending the generic with TodoI. You don’t necessarily have to extend the generic with TodoI but what you must extend it with is the type {id: number}. Another way of thinking about this is that TodoI is an extension of {id: number}.

    Unlike languages such as C++ where templates (which are equivalent of generics in TS) in the function calls are accounted for, during the compile time creating multiple executable copies of the function, TS to JS transpilation does not create multiple copies of the same function. And so when you extend a generic it should account for all the properties that are necessarily expected to be on any type used in place of that generic.

    Login or Signup to reply.
  2. Where you’ve defined getNextId, the generic type T argument has no properties, equivalent to any. Your function cannot know that T has an id property.

    The simplest solution is to enforce the generic type T extends a type with an id property

    function getNextId<T extends { id: number }>(items: T[]): number {
      return items.reduce((max, x) => (x.id > max ? x.id : max), 0) + 1;
    }
    

    FYI because the type passed to your function is inferred by the argument, you don’t need to use generics when calling it

    const id = getNextId(todoItems);
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search