0

I have a function like this

interface Cat {
    color: string,
    weight: number,
    cute: Boolean, // eventhough all cats are cute!
}

export const doSomething = (
    cat: Array<Cat| null>,
    index: number,
    key:  keyof typeof cat,
    payload: string | number | Boolean
) => {
    ....
    cat[key] = payload
    ....
}

This gives me

Element implicitly has an 'any' type because expression of type 'string' can't be used to index type

Which I understand is because TypeScript thinks that key can be any string instead one of "color", "weight", "cute". How would I tell in the function declaration that key is one of the three ("color", "weight", "cute")?

I tried

...
key:  keyof Cat,
...

Without luck. This

 cat[key] = payload

Gives me now

Type 'string| number | Boolean | ' is not assignable to type '(string & number & Boolean )

2
  • But cat is an array of Cat elements. So its keys are array indices, and thus integers. Commented Feb 2, 2023 at 11:17
  • @GabrielePetrioli I know. Is there a way to tell TypeScript that the string passed to key must be one of the keys ("color", "weight", "cute") defined in Cat? Commented Feb 2, 2023 at 11:19

1 Answer 1

2

cat is an array so keyof typeof cat are the keys of the array not the Cat interface. For those you can use keyof Cat

export const doSomething = (
    cat: Array<Cat | null>,
    index: number,
    key: keyof Cat,
    payload: string | number | Boolean
) => {
    cat.forEach(c => {
        if (c) {
            c[key] = payload
        }
    });
}

This still doesn't quite work, because there is no correlation between key and payload you could call doSomething(.., ..., 'cute', 1).

You need to add a generic type parameter to tie the payload to the key:

export const doSomething = <K extends keyof Cat>(
    cat: Array<Cat | null>,
    index: number,
    key: K,
    payload: Cat[K]
) => {
    let c = cat[index];
    if (c) {
        c[key] = payload
    }
}

Playground Link

The K in the code above is called a generic type parameter. A function can have generic type parameters in order to capture extra information from the call site making it a generic function. K extends keyof Cat means that K must be a subtype of keyof Cat so it could be one of "color", "weight", "cute".

So when we call doSomething([], 0, "cute", true), K will be "cute". Since we now have the information about which key the function was called with, we can use it in a type query (Cat[K]) to get the actual type of the property with key "cute".

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

2 Comments

I understand. Could you explain what this line <K extends keyof Cat> does? I understand what keyof Cat means, but not in cunjunction with extends, <> and before the ().
@four-eyes added more explanations

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.