#Providing a type argument and inferring the keyof without providing it

1 messages · Page 1 of 1 (latest)

golden galleon
#

This FEELS like it should be possible but I cannot figure out how to actually achieve it, and I couldn't seem to figure out how to find or ask for it either, so I believe I'm in need of a human being's help...

Essentially, I want to be able to call my function like this: updateObj<myType>("num", 3) where the first argument of the function is a keyof myType, and the second argument is a value in the type of myType["num"].

I thought I would be able to do it like this, but typescript insists that I have to provide both the type AND the key this way. Am I using the syntax wrong / missing a way to actually achieve this?

https://www.typescriptlang.org/play/?#code/C4TwDgpgBAtiAq5oF4oG8BQVtQEQDsBXGXALiiJgCMIAnAGix1wGdhayo3aBLfAc0Y48VAPaiANpzGSIAQ3wYAvhgwAzQvgDGwHqPxRCYACZzgEAPJUAVgB549KAGkoEAB7n8xllADWEEFE1KHgAPgAKfxByJ0d8CAB3ADU5CVJ4AG0nAF0ASnRVFQwAemKoAFE3SB0IYygAJihQSCg5Wn5iCHxgFkcqQmAoflFBgEYAOnD6gFZpgA5ckrLhFagANwwjU3MrOzhESAiCYlxHAGZF1SA

foggy harborBOT
#

@golden galleon Here's a shortened URL of your playground link! You can remove the full link from your message.

hedgewizardly#0

Preview:```ts
type myType = {
num: number
str: string
bool: boolean
}

function updateObj<T, K extends keyof T>(
key: K,
newVal: T[K]
) {}

// Expected 2 type arguments, but got 1.(2558)
// v
updateObj<myType>("num", 3)```

golden galleon
#
function updateObj2<T>(newobj: { [K in keyof T]: { key: K; val: T[K] } }[keyof T]) {

}

updateObj2<myType>({key:"num", val:3})

This is the only way I can find to do it otherwise, but I'd rather not have to structure an object for the parameter each time I call the function

vestal grail
#

ts doesn't have partial generic application, either you specify them all or you let all of them infer
(technically default generics exist but they don't work great with inference)

vestal grail
foggy harborBOT
#
chris.sophos#0

Preview:```ts
...
function updateObj<T extends object>(
...[key, newVal]: {
[K in keyof T]: [K, T[K]]
}[keyof T]
) {}

updateObj<myType>("num", 3)
...```

wraith grove
#

i don't get what the implementation of this function would be. where does the object that's being updated come from?

vestal grail
#

but yeah this seems like kind of a weird function to have

golden galleon
#

It's a generic state updater function in react, so each form field can pass its onChange state into this function, allowing me to set up forms for different object types more efficiently!

That spread tuple works fantastically for this, thank you!

#

!resolved

wraith grove
#

or is this a higher-order function which returns an updater (typed as (obj: T) => T)?

golden galleon
#

I'm not passing in the state, I'm passing in the event target value from the input like so:

onChange={(e)=>updateModifier<tModifier>("tldr", e.target.value)}

function updateModifier<T extends object>(...[key, val]: { [K in keyof T]: [K, T[K]] }[keyof T]) {
    setNewModifier((prev)=>{
        if(prev==undefined) {
            let newVal = {...defaultModifier(), [key]:val}
            return validateModifier(newVal)

        } else {
            let newVal = {...prev, [key]:val}
            return validateModifier(newVal)
        }
    })
}

I could potentially pass in the state as well, but the state is an object union, and some of these form fields are only applicable to one of the union types, so for example I can then provide that directly:

onChange={(e)=>updateModifier<tStaticModifier>("static_value", Number(e.target.value))}

wraith grove
#

i don't know what setNewModifier is but i'm confused as to why this is generic at all. why not just the following?

function updateModifier(key: PropertyKey, value: unknown): void
golden galleon
#

It's a SetStateAction from const [newModifier, setNewModifier] = React.useState<tModifier>(defaultModifier())

My thinking is that I can take this updateObj function (which in my snippet above is named updateModifier, though that's partly irrelevant) and then each time I make a new react form component, I can set up the state, then add this function, and inside that function I can start to add some optional additional validation logic for that specific form object.

Lastly I am able to make each form input component and reuse my function on each one's onUpdate. When I do so, Typescript will warn / remind me if I have to check for any non-string types.

I could absolutely just pass the event as an unknown / string directly into the updater function and then set up a switch case for each potential input, but I like that this method instantly reminds me to account for the new property as opposed to a switch statement that would only throw an error if it falls to the default, and I also like that for simpler objects that only have string properties, it's quicker to implement

wraith grove
#

i'm not a react guy but i think i still mostly understood that. what i'd probably do is have a function which creates updateModifier (just like how useState creates setNewModifier):

const [newModifier, setNewModifier] = React.useState<tModifier>(defaultModifier())
const updateModifier = makeUpdateModifier(setNewModifier)

then you don't have to redundantly specify any type arguments because they can be inferred from setNewModifier

#

it seems like what you're doing now isn't safe; you could write onChange={(e)=>updateModifier<SomeCompletelyUnrelatedType>("tldr", whatever)} and there would be no errors from the type checker, right?