Sorry if this is a dupe, I'm new to TypeScript and am having trouble figuring out if similar-looking questions are related because a lot of them are doing very complex things. Anyway the problem is, I have a relatively simple setup that TS is choking on because it's coercing a type to never
and I don't really understand why it's doing that. Here's the setup:
interface BigObject {
foo: {
a?: string
b?: string
}
bar: {
c?: string
d?: string
}
}
const instance: BigObject = {
foo: {
a: "a",
b: "b",
},
bar: {
c: "c",
d: "d",
}
}
function metafunction(bigObjProp: keyof BigObject) {
type LittleObject = BigObject[typeof bigObjProp]
// IDE hints show this ^^ as being correct, i.e. either of the two "sub interfaces"
return function (littleObjProp: keyof LittleObject) { // <== littleObjProp is resolving to never
return function (bigObject: BigObject) {
const littleObject = bigObject[bigObjProp]
return littleObject ? littleObject[littleObjProp] : "fallback value"
}
}
}
const firstClosure = metafunction("foo")
const secondClosure = firstClosure("a") // <== Argument of type "a" is not assignable to type "never"
const value = secondClosure(instance)
My expectation is that the value of value
will be "a".
I don't understand why littleObjProp
resolves to never
. My assumption would be that because LittleObject
is built from the type of the argument passed in to metafunction
, TS would pick which "sub interface" to use for any given invocation. So, for example, when I call metafunction("foo")
, TS would set LittleObject
to { a?: string; b?: string }
and thus, when I call firstClosure("a")
, it would say, "ah yes, 'a' is indeed a valid key of LittleObject, carry on". It can't do this, however, because it always thinks that keyof LittleObject
means never
.
Can someone help me understand 1) why it's doing this and 2) how to accomplish what I'm trying to do? I realize it's a weird setup but I'm dealing with some weird React libraries and this is just where I am at the moment. Please assume I have to keep the same overall structure of a function returning a function returning a function as seen in the example. Also I would really appreciate it if you could keep your answer as simple as possible since I am a bit new to TS. Thanks in advance!
Make metafunction
generic.
As you have it above, there's no generic type. To be safe firstClosure
is only going to take a key that foo
and bar
have in common, but they have no key in common so never
is the only possible parameter. If you were to give them a key in common, firstClosure
would be typed to accept that.
interface BigObject {
foo: {
a?: string
b?: string
f?: string // Added
}
bar: {
c?: string
d?: string
f?: string // Added
}
}
const instance: BigObject = {
foo: {
a: "a",
b: "b",
f: "f",
},
bar: {
c: "c",
d: "d",
f: "f",
}
}
const secondClosure = firstClosure("f") // "f" is the only valid value
By adding a generic, you can convince Typescript to keep the type information as a property of metafunction
and firstClosure
each, which gives your closure the type you're looking for.
function metafunction<T extends keyof BigObject>(bigObjProp: T) {
type LittleObject = BigObject[T]
return function (littleObjProp: keyof LittleObject) {
return function (bigObject: BigObject) {
const littleObject = bigObject[bigObjProp]
return littleObject[littleObjProp] ?? "fallback value"
}
}
}