Reputation: 2307
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.
Upvotes: 111
Views: 73761
Reputation: 21161
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 } />) }
...
);
};
Upvotes: 28
Reputation: 1304
These answers show the general idea, but they don't allow you to pass children like:
<MyParentComponent>
{condition && <Child1/>}
{list.map((it) => <Child2 x={it}/>}
</MyParentComponent>
I took some inspiration from the definition of children
in type PropsWithChildren<P>
from the React (v16.14.21) codebase:
type PropsWithChildren<P> = P & { children?: ReactNode | undefined };
type ReactText = string | number;
type ReactChild = ReactElement | ReactText;
interface ReactNodeArray extends Array<ReactNode> {}
type ReactFragment = {} | ReactNodeArray;
type ReactNode = ReactChild | ReactFragment | ReactPortal | boolean | null | undefined;
and came up with a simplified definition that fits my use case:
type TypedReactNode<T> = ReactElement<T> | Array<TypedReactNode<T>> | null | undefined;
type PropsWithTypedChildren<P, C> = P & { children?: TypedReactNode<C> | undefined };
Finally, I can define my component like so:
type MyParentComponentProps = {
whatever: string;
};
const MyParentComponent = (props: PropsWithTypedChildren<MyParentComponentProps, AllowedChildType>) => {
// body
}
Upvotes: 0
Reputation: 392
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;
})}
Upvotes: 5
Reputation: 1427
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>
}
Upvotes: 64
Reputation: 636
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.
Upvotes: -4