Issue
I'm creating a simple schema-like type to dynamically generate tables using React.
Those are the types that I wrote:
type MySchemaField<T, K extends keyof T> = {
key: K;
label: React.ReactNode;
render?: React.ComponentType<{item: T, value: T[K]}>;
};
type MySchema<T> = {
fields: MySchemaField<T, keyof T>[]; // <-- here is the problem: I don't want to specify the parameter K here because it depends on the single field
};
And this is how I'm expecting to use the schema:
type MyModel = {
name: string;
slug: string;
location: {address: string, country: string};
};
const schema: MySchema<MyModel> = {
fields: [
{key: 'name', label: 'Name'},
{key: 'slug', label: 'Link', render: ({value}) => value &&
<a href={`/my-path/${value}`} target="_blank" rel="noreferrer">{value}</a> || null}, // value should be inferred as string
{key: 'location', label: 'Address', render: ({value}) => <>{value.country}</>, // value should be {address, country}
],
};
I then have some React components accepting the values T and the schema MySchema<T>.
The underlying (React) code works just fine but I cannot find a way to correctly have value as a field of the type T[key]. Is there a way to do what I want or should I model differently the schema?
Solution
You want the fields property of MySchema<T> to be a union of MySchemaField<T, K> for every K in keyof T. That is, you want to distribute MySchemaField<T, K> across unions in K.
There are different ways to do this. My approach here would probably be to make a distributive object type (as coined in microsoft/TypeScript#47109), where you make a mapped type and immediately index into it. Something like {[K in KS]: F<K>}[KS] will end up being a union of F<K> for every K in KS. In your case KS is keyof T, and F<K> is MySchemaField<T, K>. Like this:
type MySchema<T> = {
fields: Array<{ [K in keyof T]-?: MySchemaField<T, K> }[keyof T]>;
};
Let's see how that works:
type MySchemaMyModel = MySchema<MyModel>
/* type MySchemaMyModel = {
fields: (
MySchemaField<MyModel, "name"> |
MySchemaField<MyModel, "slug"> |
MySchemaField<MyModel, "location">
)[];
} */
That's what you want, right? And now everything is inferred as you desire:
const schema: MySchema<MyModel> = {
fields: [
{ key: 'name', label: 'Name' },
{
key: 'slug', label: 'Link', render: ({ value }) => value &&
<a href={`/my-path/${value}`} target="_blank" rel="noreferrer">{value}</a>
|| null
},
{ key: 'location', label: 'Address', render: ({ value }) =>
<>{value.country}</>, }
],
};
Answered By - jcalz
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.