Jimmy
Jimmy

Reputation: 3880

Type 'Element[]' is missing the following properties when handling react children

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;

enter image description here

Upvotes: 7

Views: 16764

Answers (1)

Ajeet Shah
Ajeet Shah

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.

Edit:

PS: The else block <div className={styles.singleContainer}>{children}</div> and the if condition with count is not really required.

Upvotes: 13

Related Questions