1

I'm trying to figure out how to write the correct type for the Data entry so that it accepts any component with props

Example:

const A = (props: { a: number }) => <>{props.a}</>
const B = (props: { b: string }) => <>{props.b}</>

type Data = {
  [id: string]: <P>(props: P) => JSX.Element | null
}

const data: Data = {
  aa: (props: Parameters<typeof A>[0]) => A(props),
  bb: (props: Parameters<typeof B>[0]) => B(props)
}

const Usage = () => (
  <>
    A: {data.aa({ a: 12 })}
    B: {data.bb({ b: 'hi' })}
  </>
)

Even though I've specified prop types for each component, I'm still getting this error:

TS2322: Type '(props: Parameters<typeof A>[0]) => JSX.Element' is not assignable to type '<P>(props: P) => Element | null'.
  Types of parameters 'props' and 'props' are incompatible.
    Type 'P' is not assignable to type '{ a: number; }'

What should I write in the Data type so it accepts any component?

2 Answers 2

1

Provided you have installed @types/react and @types/react-dom you will have most react types already defined.

For Function Components there is the type FunctionComponent or FC for short. It is also generic so you can specify the props type.

import type { FC } from 'react';

const A: FC<{ a: number }> = ({ a }) => <>{a}</>;
const B: FC<{ b: number }> = ({ b }) => <>{b}</>;

type Data = {
  [id: string]: FC<any>;
};

const data: Data = {
  aa: A,
  bb: B,
};

const Usage = () => (
  <>
    A: {data.aa({ a: 12 })}
    B: {data.bb({ b: 'hi' })}
  </>
);

demo: https://stackblitz.com/edit/react-ts-y9ejme?file=App.tsx


Note however you lose the props type by typing an object as Data, because you're allowing any props types. Typescript will not see that b is supposed to be a number, not a string. It would be better to exclude that type and just let typescript infer the type from the object literal.

const A: FC<{ a: number }> = ({ a }) => <>{a}</>;
const B: FC<{ b: number }> = ({ b }) => <>{b}</>;

const data = {
  aa: A,
  bb: B,
};

export default function App() {
  return (
    <>
      A: {data.aa({ a: 12 })}
      {/* Error: Type 'string' is not assignable to type 'number'. */}
      B: {data.bb({ b: 'hi' })} 
    </>
  );
}

demo: https://stackblitz.com/edit/react-ts-j8uagi?file=App.tsx


Otherwise, you'll have to explicitly type the properties:

const A: FC<{ a: number }> = ({ a }) => <>{a}</>;
const B: FC<{ b: number }> = ({ b }) => <>{b}</>;

type Data = {
  aa: typeof A;
  bb: typeof B;
};

const data: Data = {
  aa: A,
  bb: B,
};

export default function App() {
  return (
    <>
      A: {data.aa({ a: 12 })}
      {/* Error: Type 'string' is not assignable to type 'number'. */}
      B: {data.bb({ b: 'hi' })}
    </>
  );
}

demo: https://stackblitz.com/edit/react-ts-ykbq8q?file=App.tsx


Or ensure that the properties you are accessing are common for all components:

const A: FC<{ a: number }> = ({ a }) => <>I'm an A {a}</>;
const B: FC<{ a: number, b: number }> = ({ a, b }) => <>I'm a B {a}</>;

type Data = {
  [id: string]: FC<{ a: number }>;
};

const data: Data = {
  aa: A,
  bb: B,
};

export default function App() {
  return (
    <>
      A: {data.aa({ a: 12 })}
      {/* Error: Type 'string' is not assignable to type 'number'. */}
      B: {data.bb({ a: 'hi' })}
      {/* You can pass b here, but it won't be typechecked */}
    </>
  );
}

demo: https://stackblitz.com/edit/react-ts-atcwwh?file=App.tsx


There's other options, but it really depends on your use case.

Sign up to request clarification or add additional context in comments.

1 Comment

We turn on linters to dissuade the use of the 'any' type (otherwise why use typescript?) so I'd be partial to the second and third options above
0

I like what Chris Hamilton mentioned with using functional components class from react. I read this question more about how to deal with typescript generic types with constraints. The best I could see is to create a union type of AProps and BProps for defining the type constraint in the Data type definition and using typeguards to enforce what is used in each Data function. It however, would get unwieldy if you want this to work for any type of component at all because you have to define the union type for all possible components.

type AProps = { a: number };
type BProps = { b: string };

const A = (props: AProps) => <>{props.a}</>
const B = (props: BProps) => <>{props.b}</>

type GenericParams = AProps | BProps;

type Data = {
    [id: string]: <P extends GenericParams>(props: P) => JSX.Element | null
}

const data: Data = {
    aa: (props: GenericParams) => isAProps(props) ? A(props) : null,
    bb: (props: GenericParams) => isBProps(props) ? B(props) : null
}

const Usage = () => (
    <>
        A: {data.aa({ a: 12 })}
        B: {data.bb({ b: 'hi' })}
    </>
)

function isAProps(props: GenericParams): props is AProps
{
    if ("a" in props && typeof props.a === "number")
        return true;

    return false;
}

function isBProps(props: GenericParams): props is BProps
{
    if ("b" in props && typeof props.b === "string")
        return true;

    return false;
}

4 Comments

Please provide code as text, not as an image.
You should also be typechecking the a and b properties if you go this route. Just having an a property doesn't mean it's an A component. It needs to be of type number.
The other issue with this is say you have a C component with a prop a of type string. I could then do data.aa({ a: 'wrong prop type' }) and we would not get a compile error. It would just return null at runtime (provided you were typechecking as mentioned above).
Thanks I made those updates - I agree Chris, you're right that when the properties of two components have the same type structure, this won't give you type safety. I suppose that's a limitation of this approach. In the end, I think the best solution to the OP question is probably dependent on the context in the app and what they want to accomplish with this Data methodology

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.