I've a type with a generic to define some extra options for a function component.
type FCWithOptions<T> = React.FC<{options?: T}>;
Using this type I'm defining some components with their own extra properties
interface ComponentAOptions {
option1: string;
option2: number;
}
interface ComponentBOptions {
prop1: boolean;
prop2: Record<string, string>;
}
type ComponentAFC = FCWithOptions<ComponentAOptions>
type ComponentBFC = FCWithOptions<ComponentBOptions>
const ComponentA: ComponentAFC = ({options}) => {
return <>Component A</>
}
const ComponentB: ComponentBFC = ({options}) => {
return <>Component B</>
}
then what I need is to pass these components in a container component using an array of interfaces that contains the component to pass and the custom options of that component through a property.
interface ComponentValue<T>{
component: FCWithOptions<T>;
componentOptions?: T;
}
interface MainComponentProps<C>{
components: ComponentValue<C>[]
}
function MainComponent<C>({}: MainComponentProps<C>) {
return <>Main</>
}
What I should be able to do is something like this:
<MainComponent components={[
{component: ComponentA, componentOptions: {option1: "", option2: 2}},
{component: ComponentB, componentOptions: { prop1: true, props: {} }}
]} />
But it works only for the first element of the array, if I add other elements I get some typescript assertions
How could I achieve this? Thanks
If you have a generic type F<T> and need to represent an array of values of that type where each element of the array has a different choice of T, that is, if you need a tuple type like [F<T0>, F<T1>, F<T2>, ⋯ , F<TN>], then the best approach is probably to use a mapped tuple/array type over a tuple of just the T0, T1, etc types. That is, if you have the type type T = [T0, T1, T2, ⋯, TN] and then you can apply type MapF<T> = {[I in keyof T]: F<T[I]>} to it to get [F<T0>, F<T1>, F<T2>, ⋯ , F<TN>].
That means we should change your code to look like this:
interface MainComponentProps<C extends any[]> {
components: [...{ [I in keyof C]: ComponentValue<C[I]> }]
}
function MainComponent<C extends any[]>({ }: MainComponentProps<C>) {
return <>Main</>
}
(where we're using C in place of T and ComponentValue<⋯> in place of of F<⋯>.) The only difference is that I've wrapped the type of components with a variadic tuple type ([...⋯]) in order to hint to the compiler that we want it to infer a tuple and not an unordered array of arbitrary length when calling MainComponent. (This use of variadic tuple types is described in ms/TS#39094, "The type [...T], where T is an array-like type parameter, can conveniently be used to indicate a preference for inference of tuple types").
Let's test it out:
<MainComponent components={[
{ component: ComponentA, componentOptions: { option1: "", option2: 2 } },
{ component: ComponentB, componentOptions: { prop1: true, prop2: {} } }
]} />
// MainComponentProps<[{ option1: string; option2: number; }, { prop1: boolean; prop2: {}; }]>
Looks good. The type C is inferred as [{ option1: string; option2: number; }, { prop1: boolean; prop2: {}; }] and therefore the mapped type [...{ [I in keyof C]: ComponentValue<C[I]> }] matches the type of the components property.