13

I'm trying to write a component which accepts an array of instances of another component as a prop. That component is MyComponent, which accepts an array of instances of Title as one of its arguments. However, I am not able to get TypeScript to type check this:

import * as React from "react";


type TitleProps = {
    name: string;
};

function Title(props: TitleProps) {
    return null;
}

type MyComponentProps = {
    titles: Array<React.ReactElement<TitleProps>>;
}

function MyComponent({ titles }: MyComponentProps) {
    return <div>{titles}</div>;
}

function OtherComponent(props: {}) { return null }

const shouldError = <MyComponent titles={[ <div/>, <OtherComponent/> ]} />;
const shouldNotError = <MyComponent titles={[ <Title name="hi"/>, <Title name="hi2"/>, ]} />;

As you can see, I am able to pass whatever I want to the titles prop, not just instances of <Title/>.

TypeScript Playground URL

David Gomes
  • 5,644
  • 16
  • 60
  • 103
  • I expect the playground to error on the last line since I'm passing a `
    ` to `titles` which should be an array of `Title` instances like `[, ...]`..
    – David Gomes Jul 25 '21 at 19:45
  • @DavidGomes I understand what is the error but that playground does not add anything new to the question. When you add a link to playground, people will expect they can try it there. – Sulthan Jul 25 '21 at 20:05
  • 2
    I think the issue is more related to the way typescript handles JSX, there is no error because any element is JSX.Element, which is also a ReactElement when we use react to handle JSX. There is no way to restrict elements by the JSX tags, since, essentially, they are the result of a function, and the props or tag no longer matter. This answer has a more detailed explanation: https://stackoverflow.com/a/59418019/10922948 – Alykam Burdzaki Jul 25 '21 at 20:11
  • PS why are you passing child components as props instead of nesting components/using React children? – Codebling Jul 29 '21 at 15:39

2 Answers2

4

TypeScript is not a silver bullet. There are a number of cases in which guaranteeing 100% type safety requires the introduction of additional complexity in the code, or is simply impossible. You have encountered one such situation.

There is an open GitHub issue on the subject of typing JSX elements. Your best bet is to wait for TypeScript to introduce a specific mechanism for this.

Otherwise (from Matt McCutchen's answer):

all JSX elements are hard-coded to have the JSX.Element type, so there's no way to accept certain JSX elements and not others. If you wanted that kind of checking, you would have to give up the JSX syntax, define your own element factory function that wraps React.createElement but returns different element types for different component types, and write calls to that factory function manually.

So as of right now there is no way to ensure type safety for any JSX elements

Codebling
  • 10,764
  • 2
  • 38
  • 66
4

The feature of type safety for JSX.Element in Typescript is not available to us yet.

We had this similar scenario before and what we did is instead of having an array of JSX.Element, we use an array of props that the same element can have. Then during render, we simply map the array of props to the array of JSX.Element.

import * as React from "react";

type TitleProps = {
  name: string;
};

function Title(props: TitleProps) {
  return null;
}

type MyComponentProps = {
  titles: TitleProps[];
};

function MyComponent({ titles }: MyComponentProps) {
  return (
    <React.Fragment>
      {titles.map(titleProps => (
        <Title {...titleProps} />
      ))}
    </React.Fragment>
  );
}

// no errror
const shouldNotError = (
  <MyComponent titles={[{ name: "React" }, { name: "fixme" }]} />
);

//error
const shouldError = (
  <MyComponent titles={[{ other: "don't use this" }, { another: "help" }]} />
);
Subrato Pattanaik
  • 5,331
  • 6
  • 21
  • 52