Reputation: 3880
I have a parent component that provides its children to a child component. The children can either be a node or an array of nodes, and depending on if it is a node or an array of nodes the children in the ChildComponent are rendered differently. However, when I a render the ChildComponent in the ParentComponent, I get the following Typescript error:
TS2786: 'ChildComponent' cannot be used as a JSX component. Its return type 'Element | Element[]' is not a valid JSX element. Type 'Element[]' is missing the following properties from type 'Element': type, props, key
Please see the below for the code. How can I render the ChildComponent without any Typescript errors? Thanks!
import React from 'react';
import styles from './component.scss';
interface Props {
children: React.ReactChildren;
}
const ChildComponent = ({ children }: Props): JSX.Element | JSX.Element[] => {
if (React.Children.count(children)) {
return React.Children.map(children, (child) => (
<div className={styles.childContainer}>{child}</div>
));
}
return <div className={styles.singleContainer}>{children}</div>;
};
const ParentComponent: React.FC<Props> = ({ children }) => (
<div className={styles.container}>
<ChildComponent>{children}</ChildComponent>
</div>
);
export default ParentComponent;
Upvotes: 7
Views: 16764
Reputation: 19863
In your current code, the return type is JSX.Element[]
from if
block (incorrect) but JSX.Element
(correct) from else
part. That's the reason of the error you are seeing.
To fix it, you need to return single JSX expression from ChildComponent
as "JSX expressions must have one parent element".
You can use a parent element like div
, span
or simply use React.Fragment
(shorthand <></>
):
const ChildComponent = ({ children }: Props) => {
if (React.Children.count(children)) {
return (
<> // HERE
{React.Children.map(children, (child) => (
<div className={styles.childContainer}>{child}</div>
))}
</>
);
}
return <div className={styles.singleContainer}>{children}</div>;
};
And children
type should be ReactNode
instead of ReactChildren
:
interface Props {
children: React.ReactNode;
}
As these are the definitions of both the typings:
type ReactNode = ReactChild | ReactFragment | ReactPortal | boolean | null | undefined;
interface ReactChildren {
map<T, C>(children: C | C[], fn: (child: C, index: number) => T):
C extends null | undefined ? C : Array<Exclude<T, boolean | null | undefined>>;
forEach<C>(children: C | C[], fn: (child: C, index: number) => void): void;
count(children: any): number;
only<C>(children: C): C extends any[] ? never : C;
toArray(children: ReactNode | ReactNode[]): Array<Exclude<ReactNode, boolean | null | undefined>>;
}
Also, I would suggest not to use React.FC.
Here is a demo.
PS: The else block <div className={styles.singleContainer}>{children}</div>
and the if
condition with count
is not really required.
Upvotes: 13