Search code examples
reactjstypescripttypescript-genericsreact-typescripttypescript-definitions

Is there a way to use generics to type a complex object array that accurately compares a React component and its props type?


My team has created typescript types definitions for an API that we're using, so we aren't able to touch the source code, just the definitions.

We have a function called add that essentially adds one or more react components to the main program. The props property of the object should depend on the component type listed on the component property.

It looks like this:

add(arg1, arg2, {
//some other args
children: [{
  id: string1,
  //BackButton can be a functional or class-based component
  component: BackButton,
  //BackButtonProps will be defined within BackButton
  props: BackButtonProps
},
  {
  id: string2,
  //SkipButton can be a functional or class-based component
  component: SkipButton,
  //SkipButtonProps will be defined within SkipButton
  props: SkipButtonProps
}
]
});

For reference, there is an alternate (overloaded) version of the function that only adds one component instead of multiple that I've been able to figure out the typescript for. It looks like this:

add<T>(
    id: string,
    component: ComponentType<T>,
    props: T
  ): void;

My question is this -- since the number of children components is unknown, how can I use a generic on the add function so that all the children components are correctly typed (the props and component properties match up)?


Solution

  • A generic signature of add would look like this.

    declare function add<T extends any[]>(arg1: any, arg2: any, arg3: {
        children: [...{ [K in keyof T]: {
          id: string,
          component: T[K]
          props: React.ComponentProps<T[K]>     
        }}]
    }): void
    

    The generic type T would store a tuple where each element is a react component. For each element with the index K of the passed array, the component type would be used as the type in T[K] and the props type would have to be React.ComponentProps<T[K]>.

    Let's see this in action:

    const BackButton = (props: {a: string}) => {
        return <div></div>
    }
    
    const SkipButton = (props: {b: string}) => {
        return <div></div>
    }
    
    add(0, 0, {
        children: [
            {
                id: "1",
                component: BackButton,
                props: { a: "123" }
            },
            {
                id: "2",
                component: SkipButton,
                props: { b: "123" }
            },
            {
                id: "3",
                component: SkipButton,
                props: { a: "123" } // Error: Type '{ a: string; }' is not assignable to type '{ b: string; }'
            }
        ]
    })
    

    Playground