Reputation: 458
Suppose I have the following objects:
const arrayOfDifferentComponents: HowDoITypeThis = [
{
component: ComponentOne, // no error, keys and value types match
inputs: {
key1: "foo"
key2: 1
}
},
{
component: ComponentTwo, // error, key2 should be boolean
inputs: {
key1: ["foo"]
key2: 1
}
}
]
class ComponentOne {
key1!: string;
key2!: number;
}
class ComponentTwo {
key1!: Array<string>;
key2!: boolean;
}
Is it possible to write the type HowDoITypeThis
without generics, such that the inputs
in the first array item only allow keys of ComponentOne
and the inputs
in the second item only allow keys of ComponentTwo
?
Just to clarify, I want this type to work with a dynamic number of components and component types.
Upvotes: 2
Views: 193
Reputation: 69
you can use typescript tuple such as
type HowDoITypeThis = [
{
component: ComponentOne;
inputs: {
someKeyOfComponentOne: ComponentInputA;
};
},
{
component: ComponentTwo;
inputs: {
someKeyOfComponentTwo: ComponentInputB;
};
}
]
of course you can also do this
interface MyGenericA<T, U> {
component: T;
inputs: U;
}
type HowDoITypeThis = [
MyGenericA<ComponentOne, YourInputTypeA>,
MyGenericA<ComponentTwo, YourInputTypeB>
]
Upvotes: 2
Reputation: 42238
You can used mapped types to create a union of possible pairings, but this has some limitations. It will work with a dynamic number of component/type pairs, but not an unknown number.
When you are creating a union through mapped types, basically what you do is create a key-value object type and then take the union of all the values. So the keys get discarded, but we need some sort of key at some point in order to do the mapping from ComponentOne
to {component: ComponentOne; inputs: React.ComponentProps<ComponentOne>}
. I'm struggling with what that key would be in this scenario as I'm not seeing any sort of discriminant.
(side note: I find your naming to be confusing because your ComponentOne
is the props type rather than the component type, so I'm using names that are clearer.)
If you define some sort of map like this:
type PropTypes = {
one: ComponentOneProps;
two: ComponentTwoProps;
}
Then you can use a mapped type like this:
type ComponentAndProps = {
[K in keyof PropTypes]: {
component: React.ComponentType<PropTypes[K]>;
inputs: PropTypes[K];
}
}[keyof PropTypes];
Which gives you the union of all valid pairings:
type ComponentAndProps = {
component: React.ComponentType<ComponentOneProps>;
inputs: ComponentOneProps;
} | {
component: React.ComponentType<ComponentTwoProps>;
inputs: ComponentTwoProps;
}
Your HowDoITypeThis
is an array ComponentAndProps[]
. You'll get a big red error if you try to assign ComponentOneProps
to a ComponentTwo
component.
You need a different approach if you want your array to accept any type of component, but enforce that the component
and input
properties match. This does require generics. It also requires that you create arrayOfDifferentComponents
through a function because we cannot say its specific type. We need to infer its generic and check that that the provided array is correct for that generic.
You can create a mapped type that maps from a tuple of prop types to a tuple of component
/inputs
pairs:
type MapToPairing<T> = {
[K in keyof T]: {
component: React.ComponentType<T[K]>;
inputs: T[K];
}
}
And use an identity function to make sure that your array is valid:
const createComponentArray = <T extends {}[]>(array: MapToPairing<T>) => array;
You do get the expected error when your array includes an element with mismatched component
and inputs
properties.
Upvotes: 3
Reputation: 4415
you basically had it:
type HowDoITypeThis = [
{
component: ComponentOne,
inputs: {
someKeyOfComponentOne: ComponentOne[someKeyOfComponentOne]
}
},
{
component: ComponentTwo,
inputs: {
someKeyOfComponentTwo: ComponentTwo[someKeyOfComponentTwo]
}
}
]
Upvotes: 2