Home > Software design >  Index children of React component with TypeScript
Index children of React component with TypeScript

Time:11-26

I'm making a component that has multiple sets of children.

The question React component with two sets of children suggests to index children, as in props.children[0]. This works great in JavaScript.

But in TypeScript, I'm getting a type error, even though the code works fine at runtime.

function MyComponent(props: { children: React.ReactNode }) {
  return <>
    ...
    {props.children[0]}
    ...
    {props.children[1]}
    ...
  </>;
}    

TypeScript fails with the following error messages on props.children[0]:

Object is possibly 'null' or 'undefined'.
ts(2533)
Element implicitly has an 'any' type because expression of type '0' can't be used to index type 'string | number | boolean | ReactElement<any, string | JSXElementConstructor<any>> | ReactFragment | ReactPortal'.
  Property '0' does not exist on type 'string | number | boolean | ReactElement<any, string | JSXElementConstructor<any>> | ReactFragment | ReactPortal'.
ts(7053)

How do I make it typecheck?

CodePudding user response:

You need to tell Typescript that your component needs multiple children by putting the array brackets:

function MyComponent(props: { children: React.ReactNode[] }) {
// ...
}

If your component accepts exactly 2 children, a tuple type would be preferable so that Typescript will enforce having 2 children:

function MyComponent(props: { children: [React.ReactNode, React.ReactNode] }) {
// ...
}

If your component accepts 2 or more children, you can specify a tuple with a rest element:

function MyComponent(props: { children: [React.ReactNode, ...React.ReactNode[]] }) {
// ...
}

CodePudding user response:

You can use ReactNode[] instead of ReactNode

function MyComponent(props: { children: React.ReactNode[] }) {
  return <>
    {props.children[0]}
    {props.children[1]}
  </>;
}

function TestComponent() {
  return <MyComponent>
    <span>Element 1</span>
    <span>Element 2</span>
    {'string test'}
    {2}
    {true}
  </MyComponent>
}

Playground

CodePudding user response:

If your component always attempts to render at least 2 child nodes, then you should explicitly type the children parameter that way:

TS Playground

import {type ReactElement, type ReactNode} from 'react';

function MyComponent(props: {
  children: [ReactNode, ReactNode, ...readonly ReactNode[]];
}): ReactElement {
  return <>
    ...
    {props.children[0]}
    ...
    {props.children[1]}
    ...
  </>;
}

By doing so, TypeScript will emit a compiler error diagnostic if you don't provide at least two children:

function App (): ReactElement {
  return (
    <MyComponent>{/*
     ~~~~~~~~~~~
  Type '{ children: []; }' is not assignable to type '{ children: [ReactNode, ReactNode, ...ReactNode[]]; }'.
    Types of property 'children' are incompatible.
      Type '[]' is not assignable to type '[ReactNode, ReactNode, ...ReactNode[]]'.
        Source has 0 element(s) but target requires 2.(2322) */}
    </MyComponent>
  );
}

Similarly, when you only provide one child node:

TS Playground

function App (): ReactElement {
  return (
    <MyComponent>{/*
     ~~~~~~~~~~~
    This JSX tag's 'children' prop expects type '[ReactNode, ReactNode, ...ReactNode[]]'
    which requires multiple children, but only a single child was provided.(2745) */}
      <div></div>
    </MyComponent>
  );
}

But as soon as you provide at least two, the diagnostic disappears:

TS Playground

function App (): ReactElement {
  return (
    <MyComponent>
      <div></div>
      <div></div>
    </MyComponent>
  );
}
  • Related