Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why typescript does not properly infer T[K] with <T extends Person, K extends keyof T> generic?

I have created a React hook:

interface Person {
    name: string
    age: number
}

export const usePerson = function <T extends Person, K extends keyof T>(): (property: K, setter: (value: T[K]) => T[K]) => void {

    const setPersonProperty = (property: K, setter: (value: T[K]) => T[K]): void => {
        console.log(property, setter)
    }

    setPersonProperty('name', (x) => x)

    return setPersonProperty;
}

I have seen the pattern <T, K extends keyof T> from typescript docs but I am unable to use it correctly in my example.

More specifically, the typescript complains on the line

setPersonProperty('name', (x) => x)

Although when I start typing setPersonProperty('n /* IDE autocompletes name */ but complains for the 'name' parameter and I get the following error:

Argument of type 'string' is not assignable to parameter of type 'K'.   'string' is assignable to the constraint of type 'K', but 'K' could be instantiated with a different subtype of constraint 'string | number | symbol'.

I have also posted an image from my IDE. I am using typescript 4+. [![enter image description here][1]][1]

What am I doing wrong here ? [1]: https://i.stack.imgur.com/WzJE5.png

like image 893
entropyfeverone Avatar asked Dec 18 '21 20:12

entropyfeverone


1 Answers

I bet that this is the result You want to achieve

type Person = {
    name: string,
    age: number
};

export const usePerson = function <T extends Person>() {
    const setPersonProperty = <K extends keyof T> (property: K, setter: (value: T[K]) => T[K]): void => {
        console.log(property, setter)
    }

    setPersonProperty("name", (x) => x)

    return setPersonProperty;
}

 const setPerson = usePerson<Person>()

 setPerson("name", (name) => name)

Basically, in code you provided there are two generic types when calling this usePerson hook T and K (<T extends Person, K extends keyof T>).

Let's say someone will call this function with usePerson<Person, "age">(). What typescript will do it will replace types inside our usePerson to be T = Person and K = "age"

And now you have a mismatch since setPersonProperty have first argument of type K ("age") the argument "name" is not assignable - that the error you see.

like image 52
jkaczmarkiewicz Avatar answered Sep 27 '22 20:09

jkaczmarkiewicz