Reputation: 14620
This question uses React code but is specific to typescript rather than react.
I am using a simplified example to try and make this easier to read. I have a component MyList
that takes one generic type argument that is passed to the props type.
This generic type infers an object to be used to generate a child component. I want to key each child component using a value of T[keyof T]
|| its index in T[]
.
Creating the list and using the index is no problem but I can't figure out how to correctly type listKey
so that it will work with reacts key
property that is expecting number | string | undefined
.
type MyListProps<T> = {
list: T[],
listKey: keyof T,
contentKey: keyof T
}
class MyList<T extends object> extends React.Component<MyListProps<T>, any> {
public render() {
return (
<div>
{ this.props.list.map((value: T, index: number) => {
const key = value[this.props.listKey];
return (
// Type T[keyof T] is not assignable
// to type 'number | string | undefined'
<Item key={key}>{value[this.props.contentKey]}</Item>
);
}
</div>
);
}
}
How could I infer that the listKey
type is assignable to the expected type of reacts key
prop using a generic?
Upvotes: 1
Views: 584
Reputation: 250006
Since you want to assign the result of the index access to key
you will need to restrict what that index operation can return. One way to do that is to use a constraint on T
:
class MyList2<T extends Record<string, string | number >> extends React.Component<MyListProps2<T>, any> {
public render() {
return (
<div>
{ this.props.list.map((value: T, index: number) => {
const key = value[this.props.listKey];
return (
<Item key={key}>{value[this.props.contentKey]}</Item>
);
})}
</div>
);
}
}
This does have the drawback of restricting the possible values you can have in T
which might be a problem (or not depending on your use case).
Another more complicated option is to restrict just what indexing using listKey
will return. This does require an extra type parameter, but it allows more flexibility:
type MyListProps<T, LK extends PropertyKey> = {
list: Array<T & Record<LK, string>>,
listKey: LK,
contentKey: keyof T
}
declare const Item: React.FC<{}>
class MyList<T, LK extends PropertyKey> extends React.Component<MyListProps<T, LK>, any> {
public render() {
return (
<div>
{this.props.list.map((value, index: number) => {
const key = value[this.props.listKey];
return (
<Item key={key}>{value[this.props.contentKey]}</Item>
);
})}
</div>
);
}
}
let x = <MyList listKey="a" contentKey="a" list={[
{ a: "", b: 0, p: {} }
]} ></MyList>
Or you could forgo type safety in this case and just use a type assertion.
Upvotes: 1