0

What I want

I have an object and a 'consumer' function that takes a key and handler function, then passes the value for that key to the handler.

I want Typescript to infer the correct type for the handler function, given an arbitrary key at runtime.

I want this to use the key as a 'selector' for the correct handler, and have type safety in using that handler. I know I could manually type the argument in the handler given the key, but I want it to be inferred automatically to have proper type safety. For example, to break if I change the key and not the implementation of the handler.

What I've tried

Typescript playground link

type Things<T> = {
  [K in keyof T]: T[K]
}

type ThingConsumer<T, K extends keyof T> = (
  forThingOfType: K,
  handler: (value: T[K]) => void,
) => void

function createThings<T>(things: Things<T>): Things<T> {
  return things
}

function createThingConsumer<T, K extends keyof T>(
  things: Things<T>,
): ThingConsumer<T, K> {
  return (forThingOfType, handler) => handler(things[forThingOfType])
}

const things = createThings({
  a: { foo: 1, bar: 2 },
  b: { baz: 3 },
})

const thingConsumer= createThingConsumer(things)

thingConsumer('a', (value) => {
  console.log(value.foo + 1) // <-- error: Object may be 'undefined'
})

What doesn't work

It appears that Typescript can't infer the concrete type of value correctly as { foo: number, bar: number } from the passed key, 'a'.

Instead, it infers value as a union type of all possible values of things, i.e.

{ foo: number, bar: number } | { baz: number }

And then the error message when trying to use value.foo is very unexpected. It says that the "Object" may be undefined. If anything, I would have expected it to complain that foo might be undefined, given the union type inference, but not that value itself may be undefined.

Is what I'm trying to do possible?

It feels like it ought to be - in particular I really thought that the T[K] syntax says 'for a given concrete key K of an object, resolve the type of the corresponding value'. But perhaps it's not that powerful and is instead saying 'for any key, resolve any value'?

1 Answer 1

1

<K extends keyof T> should be moved as a property of the returned function rather than the higher order function.

type ThingConsumer<T> = <K extends keyof T>(
  forThingOfType: K,
  handler: (value: T[K]) => void,
) => void

See: https://www.typescriptlang.org/play?ssl=1&ssc=1&pln=30&pc=1#code/C4TwDgpgBAKgFgSwHYHMDOAeGA+KBeKAbwCgooBtAaSmSgGsIQB7AM1gF0AuWK94gX2LFQkWIlQBhJkjQBXALYQATllwEM1CAA9gEJABM09Rq1jYAFKSgsmS+MhQB5FjHARulADRW4AQwMANsrc5gBuvgGy7jyU7ACU+LihTAj63gl4SSn6QiyySADGwAjSUAVKEL669qiYOObA4ujcNeiqcS1NdbgkZBXAskpIUI0OaAK5+UUlw+WV1U1SMgrKWJ5Qmjp6hsbMbPVWo7WdY6rpJ5LScooqOERW-YPD5jZ2Tc6ukOt+gcoZuD99EElA0uuRXq0Pm54hNiAUrsARl18GUKlUIK00OZelBfNxCNYmExuABGdYAI18Sm4ACYoPxvGRyfioJSAF7cADM9O8-DiQnhMkRRxQS2uygIc3RrTFKxBIrQ-OEiyucvMAHJfOr1mEIlF-vcyIK0EwggA6AJMFC6yIQM02JhQADUUBJ-L5xCAA

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

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.