How to solve TypeScript error in React.Children when using child.type

79 Views Asked by At

In below React TypeScript component, TypeScript is complaining at child.type?

Argument of type 'string | JSXElementConstructor' is not assignable to parameter of type '(({ src, alt }: CardImageProps) => Element) | (({ children }: CardBodyProps) => Element)'. Type 'string' is not assignable to type '(({ src, alt }: CardImageProps) => Element) | (({ children }: CardBodyProps) => Element)'.

How to fix this TypeScript error?

const Card = ({ children }:CardProps) => {

  const allowed = [CardImage, CardBody];

  return (
    <div
      {React.Children.map(children, (child) => {
        if (!allowed.includes(child.type)) return;
        return child;
      })}
    </div>
  );
};

Card.Image = CardImage;
Card.Body = CardBody;

export default Card;
1

There are 1 best solutions below

6
On

The error you're facing is because of the signature of the Array.prototype.includes method. Given that your allowed array is of type JSXElementConstructor[] and child.type is a string | JSXElementConstructor, you cannot compare a string and a JSXElementConstructor.


There's some issues with your approach however:

  1. child might be undefined or null and you're directly accessing child.type.
  2. child.type might be a string (for in-built HTML tags) or a function for custom components, or even undefined for simple text.

How about filtering your children like this?

const allowed: React.JSXElementConstructor<any>[] = [CardImage, CardBody];
const filteredChildren = React.Children.toArray(children).filter((child) => {
  return (
    React.isValidElement(child) &&
    typeof child.type !== "string" &&
    allowed.includes(child.type)
  );
});

This way you make sure that your child is indeed a ReactElement, it is not a built-in HTML component, and that it is one of either of your allowed components.

In the CodeSandbox, for some reason, Array.prototype.includes doesn't seem to be defined. You can still use: allowed.indexOf(child.type) >= 0 instead of allowed.includes(child.type)


With this in mind, if you were to render something like:

<Card>
  <CardImage src="https://placeholder.com/150" />
  <CardBody>Card Text</CardBody>
  Text that won't be in card.
  <ComponentThatShouldNotBeInCard />
</Card>

Only CardImage and CardBody will be rendered.


You can check out the following CodeSandbox to help you out:

Edit React Children Filtering