How do I restrict the type of React Children in TypeScript, using the newly added support in TypeScript 2.3?

ReactjsTypescript

Reactjs Problem Overview


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.

Reactjs Solutions


Solution 1 - Reactjs

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>
}

Solution 2 - Reactjs

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 Tabs, 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 } />) }

    ...
  );
};

Solution 3 - Reactjs

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;
})}

Solution 4 - Reactjs

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.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionChristopher ArmstrongView Question on Stackoverflow
Solution 1 - ReactjsPierre FerryView Answer on Stackoverflow
Solution 2 - ReactjsDanzigerView Answer on Stackoverflow
Solution 3 - ReactjsMaor DahanView Answer on Stackoverflow
Solution 4 - ReactjsMoonjsitView Answer on Stackoverflow