skip to Main Content

I’m making a React component which accepts an array of other React components with their props.

type Components = [ComponentType, Props][]

How can I write this, so typescript will understand that Props object should match given Component props?

Thanks.

UPD, Basically a function takes an array of [fn, args] tuples and calls each fn with the corresponding args. How do I write the signature of that function so that the tuples are type-safe correct fn/arg pairings?

2

Answers


  1. You can do this, although it’s a little complicated and in its current implementation its a bit more fragile than I’d like. I’ve abstracted the type logic into a helper type:

    // This part is reusable for any pair of [unaryFunction, argument]
    type IsFnArgPair<T extends any> = T extends [infer F, infer A] ? F extends (x: A) => any ? T : [never, never] : [never, never]
    
    function RenderChildPropPairs<T extends any[]>(props: { pairs: Array<IsFnArgPair<T>> }): JSX.Element {
      return (
        <>
          {props.pairs.map(([Comp, props]) => <Comp {...props} />)}
        </>
      );
    }
    

    We can define a couple of Component/props pairs to test this:

    type MyComponentProps = { children: React.ReactNode, foo: number }
    function MyComponent(props:MyComponentProps): JSX.Element {
      return (
        <div>
          {String(props.foo)}
          {props.children}
        </div>
      );
    }
    
    type MyOtherComponentProps = {
      foo: boolean
    }
    
    function MyOtherComponent({ foo }: MyOtherComponentProps): JSX.Element {
      return <div>{String(foo)}</div>;
    }
    

    and then test it:

    function RenderPass() {
      // OK
      return <RenderChildPropPairs pairs={[[MyComponent, { foo: 1, children: null }], [MyOtherComponent, { foo: true }]]} />
    }
    
    function RenderFail() {
      // Fails as expected
      return <RenderChildPropPairs pairs={[[MyComponent, { foo:'hi', children: null }], [MyOtherComponent, { foo: 1 }]]} />
    }
    

    This isn’t perfect. In particular there are two areas you could potentially improve this: better intellisense (somebody who passes an invalid pair will just get "type blah is not assignable to type ‘never’" which isn’t a great error message. The second is that if you try to pull out the pairs into a variable you’ll see an issue with the inference, although that can be mitigated a bit by using satisfies:

    function LessOops() {
      const pairs = [[MyComponent, { foo: 1, children: null }], [MyOtherComponent, { foo: true }]] satisfies Array<[any, any]>;
      return <RenderChildPropPairs pairs={pairs} />
    }
    

    Note that this is still type safe despite the anys: if you pass an invalid pair you’ll get an error.

    Another issue with this solution is that if you try to type the array of pairs as a tuples using as const you’ll see an error relating to the readonlyness of as const. I’ll keep trying to clean this up, although maybe somebody more clever can give a better solution.

    Playground

    Login or Signup to reply.
  2. Try to use TypeScript generics to define a type for your array of components along with their corresponding props. Here’s how you can do it:

    import { ComponentType } from 'react';
    
    // Define a generic type for props
    type Props<T extends ComponentType<any>> = T extends ComponentType<infer P> ? P : never;
    
    // Define a type for an array of component-prop pairs
    type Components<T extends [ComponentType<any>, any][]> = {
      [K in keyof T]: T[K] extends [ComponentType<any>, any] ? [T[K][0], Props<T[K][0]>] : never;
    };
    
    // Example usage
    const components: Components<[
      [React.FC<{ name: string }>, { name: string }],
      [React.FC<{ age: number }>, { age: number }]
    ]> = [
      [Component1, { name: 'John' }],
      [Component2, { age: 25 }]
    ];
    

    The Props type uses a conditional type to infer the props type of a given component type. The Components type takes an array of component-prop pairs and ensures that each component’s props match the specified props type.

    You can then use the Components type to define your array of components along with their props.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search