Reputation: 911
I am trying to find a way to build a function that takes in a state, only allows specifying a property name based on a type (in this case string
) and then doing something with that property.
In the following case everything works well except within the body of the function. The line return prop.substr(0) + payload
errors because TypeScript cannot infer by this point that state[property]
could only ever refer to a property that is of type string.
type StringKeys<T> = { [K in keyof T]: T[K] extends string ? K : never }[keyof T]
const state = { one: 'string', two: 34, three: true }
type StateStringKeys = StringKeys<typeof state> // ✓ should only contain 'one'
const combineStrings = <S, P extends StringKeys<S>>(state: S, property: P, payload: S[P]): string => {
const prop = state[property]
return prop.substr(0) + payload // X - How do I tell TS that this could only ever be a string by now?
}
combineStrings(state, 'one', 'test') // ✓ should NOT error, 'one' is a string and a string is provided
combineStrings(state, 'one', 23) // ✓ should error, 'one' is a string but a number is provided
combineStrings(state, 'two', 23) // ✓ should error, 'two' is a number
combineStrings(state, 'three', 'test') // ✓ should error, 'three' is a boolean
combineStrings(state, 'four', false) // ✓ should error, 'four' does not exist
Is there any way in this to have the same type safety when calling the function but also infer the correct type of string
for prop
.
Here's a link on TypeScript Playground if you want.
Upvotes: 0
Views: 39
Reputation: 20132
The fix is to revert the constrain at the type level, force object to extend object with string values. Consider:
const combineStrings =
< S extends Record<P, string> // here we say that object has P property as string
, P extends keyof S >(state: S, property: P, payload: S[P]): string => {
const prop = state[property]
return prop.substr(0) + payload // works 👍
}
Upvotes: 1