'Covariant array of React components
I have a class I have derived from a base class, with a union of the base properties and the properties of the component. Something like:
interface BaseFooProps {
name: string;
}
type BaseFoo<T extends BaseFooProps = BaseFooProps> = React.FC<T & BaseFooProps>;
const BaseFooComponent: React.FC<BaseFooProps> = ({ name }) => <>{name}</>;
And the derived class:
interface AProps extends BaseFooProps {
height: number;
}
const A: React.FC<AProps> = ...
Then, I want to have another component that takes the array of said BaseFoo components. This is to enforce that each array member has at least the base properties:
interface OtherBarProps {
items: BaseFoo[]
};
const OtherBar: React.FC<OtherBarProps> = ...
const items = [
<A name=... height=... key=... />,
];
<OtherBar items={components} />
But that last line complains, saying that Type 'Element[]' is not assignable to type 'BaseFoo<BaseFooProps>[]'.
I've tried casting items with as ..., and casting the left-hand side, as well as adding {} to the React.FC<T & BaseFooProps> definition, but none of these solved the issue.
Why are the array's members cast so generically to Element, and not to BaseFoo? How do I encourage the type system to accept these?
Solution 1:[1]
I think you are mixing up Element and Component types. A component instance (element) like <A/> is not equivalent to the component function itself const A = () => {...}. If you want to pass the actual instances you need to use React.ReactElement.
interface OtherBarProps {
items: React.ReactElement<BaseFooProps>[]
// Or the equivalent of your original type
// items: React.ReactElement<React.ComponentProps<BaseFoo>>[]
};
const items = [
<A name="Joe" height={42} />,
];
<OtherBar items={items} />
Unfortunately, it seems the only way to actually restrict what elements are allowed is to go in and explicitly set types for everything you want to use. This is because all React components currently return the supertype JSX.Element, even if you give them a React.ReactElement<PropType> return type.
So you could do something like this:
const newA: React.ReactElement<AProps> = <A name="Joe" height={42} />;
const button: React.ReactElement<{}> = <button/>
const items = [
newA,
button
];
// Property 'name' is missing in type '{}' but required in type 'BaseFooProps'.
<OtherBar items={items} />
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 |
