I'm trying to take advantage of the recently added support for typing of children in the TypeScript compiler and @types/react, but struggling. I'm using TypeScript version 2.3.4.
Say I have code like this:
interface TabbedViewProps {children?: Tab[]}
export class TabbedView extends React.Component<TabbedViewProps, undefined> {
render(): JSX.Element {
return <div>TabbedView</div>;
}
}
interface TabProps {name: string}
export class Tab extends React.Component<TabProps, undefined> {
render(): JSX.Element {
return <div>Tab</div>
}
}
When I try to use these components like so:
return <TabbedView>
<Tab name="Creatures">
<div>Creatures!</div>
</Tab>
<Tab name="Combat">
<div>Combat!</div>
</Tab>
</TabbedView>;
I get an error as follows:
ERROR in ./src/typescript/PlayerView.tsx
(27,12): error TS2322: Type '{ children: Element[]; }' is not assignable to type 'IntrinsicAttributes & IntrinsicClassAttributes<TabbedView> & Readonly<{ children?: ReactNode; }> ...'.
Type '{ children: Element[]; }' is not assignable to type 'Readonly<TabbedViewProps>'.
Types of property 'children' are incompatible.
Type 'Element[]' is not assignable to type 'Tab[] | undefined'.
Type 'Element[]' is not assignable to type 'Tab[]'.
Type 'Element' is not assignable to type 'Tab'.
Property 'render' is missing in type 'Element'.
It seems to be inferring the type of children as just Element[]
instead of Tab[]
even though that's the only type of children I'm using.
EDIT: It would also be fine to restrict the interface of the children props instead of restricting the type of the children components directly, since all I need to do is pull some specific props out of the children components.
Edit 2: Turns out that this approach prevent the warning, but according to the comments TabProps
aren't properly checked.
You should try to set children of interface TabbedViewProps like so
interface TabbedViewProps { children?: React.ReactElement<TabProps>[] }
The idea here is not to tell your TabbedView
has an array of Tab
, but instead tell your TabbedView
he has an array of element
which takes specific props. In your case TabProps
.
Edit ( thx to Matei ):
interface TabbedViewProps {
children?: React.ReactElement<TabProps>[] | React.ReactElement<TabProps>
}
As pointer out already, declaring TabbedView.children
as:
children: React.ReactElement<TabProps> | React.ReactElement<TabProps>[];
Will get rid of the error, but it won't be type-checking the children properly. That is, you will still be able to pass children other than TabProps
to TabbedView
without getting any error, so this would also be valid:
return (
<TabbedView>
<Tab name="Creatures">
<div>Creatures!</div>
</Tab>
<Tab name="Combat">
<div>Combat!</div>
</Tab>
<NotTabButValidToo />
</TabbedView>
);
What you could do instead is declare a prop, let's say tabs: TabProps[]
, to pass down the props you need to create those Tab
s, rather than their JSX, and render them inside TabbedView
:
interface TabbedViewProps {
children?: never;
tabs?: TabProps[];
}
...
const TabbedView: React.FC<TabbedViewProps > = ({ tabs }) => {
return (
...
{ tabs.map(tab => <Tab key={ ... } { ...tab } />) }
...
);
};
I tried to assert the type. You can throw or just ignore.
interface TabbedViewProps {
children?: React.ReactElement<ITabProps> | React.ReactElement<ITabProps>[]
}
And in the component itself map the children and assert or ignore
{React.Children.map(props.children, (tab) => {
if(tab?.type != Tab) return;
console.log(tab?.type == Tab);
return tab;
})}
ab?.type != Tab
Type you are returning in Tab render method is JSX.Element. This is what causes your problem. TabbedView is expecting array of childrens with type Tab. I am not sure if you can specify a certain component as a children type. It can be string or JSX.Element. Can you show the definition file for Tab?
Look at https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/react/index.d.ts to see how JSX.Element interface looks.
Success story sharing
children?: Array<React.ReactChild> | React.ReactChild
. This way the component can accept even a single child and not necessarily an array of children.children
type like above.type ChartElement<T> = ReactElement<ChartProps<T>, string | ((props: any) => ReactElement<any, string | ... | (new (props: any) => Component<any, any, any>)> | null) | (new (props: any) => Component<any, any, any>)>
Note the|(props: any) =>
.