Reputation: 2214
I have run into a little issue with Typescript. I am trying to write some functions to make dealing with my Redux state simpler.
I have this interface that I use to create some Entity State:
export interface NormalizedState<T> {
byId: { [id: string]: T };
allIds: string[];
}
I have some MailState like this:
export interface MailState extends NormalizedState<Mail> {
inbox: string[]
}
And I have a function to add to the byId property like this:
type StringPropertyNames<T> = { [K in keyof T]: T[K] extends string ? K : never }[keyof T];
const appendToState = <T, K extends StringPropertyNames<T>>(s: NormalizedState<T>) => (v: T[], p: K) =>
v.reduce((acc, m) => ({ ...acc, [m[p]]: m }), s.byId);
Problem is I get an error in the appendToState function at
[m[p]]
I get the following error:
A computed property name must be of type 'string', 'number', 'symbol', or 'any'.
So it seems like something in my typings is off. Any help would be greatly appreciated!
Upvotes: 2
Views: 525
Reputation: 249536
Your types are fine, and for the caller they should work out ok. The problem is that inside a function Typescript can't really do a lot with conditional types. So it will not try to figure out that T[K]
would work out to a string
, it just sees the conditional and gives up.
The simple solution is to use a type assertion.
const appendToState = <T, K extends StringPropertyNames<T>>(s: NormalizedState<T>) => (v: T[], p: K) =>
v.reduce((acc, m) => ({ ...acc, [m[p] as string]: m }), s.byId);
The other solution is to reverse the way you express the condition. Instead of K
is any key of T
that has the type string
, we can say T
must have a property K
of type string
. This alternate way is easier for the compiler to reason about
const appendToState2 = <T extends Record<K, string>, K extends PropertyKey>(s: NormalizedState<T>) => (v: T[], p: K) =>
v.reduce((acc, m) => ({ ...acc, [m[p]]: m }), s.byId);
Upvotes: 4