-1

I have this component that can take a single child or multipipe children, here it is with multiple

<SideDataGridItem>
   <div id='top'>
      <div>A1</div>
      <div>B1</div>
      <div>C1</div>
   </div>
   <div id='bottom'>
       <div>A2-</div>
       <div>B2-</div>
       <div>C2-</div>
    </div>
 </SideDataGridItem>

and here it is with a single child

<SideDataGridItem>
   <div id='top'>
      <div>A1</div>
      <div>B1</div>
      <div>C1</div>
   </div>
 </SideDataGridItem>

I want to be able to map over the children of the child.

Here's my attempt that doesn't work...

import { ReactElement, Children } from 'react';

export interface PropsShape {
  children: ReactElement | ReactElement[];
}

const SideDataGridItem = ({ children }: PropsShape): ReactElement => {
  const childArr = Children.toArray(children);
  return (
    <>
      {childArr[0] &&
        childArr[0].map(function (item: any, i: number) {
          return (
            <div key={i}>
              {item}- {i}
            </div>
          );
        })}
      {childArr[1] &&
        childArr[1].map(function (item: any, i: number) {
          return (
            <div key={i}>
              {item}- {i}
            </div>
          );
        })}
    </>
  );
};
export { SideDataGridItem };

the error I get is a red squiggle on the .map...

Property 'map' does not exist on type 'ReactChild | ReactFragment | ReactPortal'.
  Property 'map' does not exist on type 'string'.

because it's trying to loop over a single item when really I want to loop over its children!

the correct answer will not output a div with an id of 'top' or 'bottom' it will simply map over the children of that div with the id

Attempt 2

import { ReactElement, Children } from 'react';

export interface PropsShape {
  children: ReactElement | ReactElement[];
}

const SideDataGridItem = ({ children }: PropsShape): ReactElement => {
  const childArr = Children.toArray(children);
  return (
    <>
    // map over the childArr
      {childArr.map(function (item: any) {
        // for each item inside the childArr map over them ?? expecting just 3 items not 4 or 1
        {
          item.map(function (itemInner: any, i: number) {
            return (
              <div key={i}>
                {itemInner}- {i}
              </div>
            );
          });
        }
      })}
    </>
  );
};
export { SideDataGridItem };

Attempt 3

If I log out childArr I get enter image description here Here we can see the array of the 3 items I want to map over in childArr[0].props.children

when I try and map over it I get

      {childArr[0] &&
        childArr[0].props.children.map(function (item: any, i: number) {
          return (
            <div key={i}>
              {item}- {i}
            </div>
          );
        })}

with the error

Property 'props' does not exist on type 'ReactChild | ReactFragment | ReactPortal'.
  Property 'props' does not exist on type 'string'.
Bill
  • 4,614
  • 13
  • 77
  • 132
  • 1
    `childArr[0]` is accessing the actual element at the given position which in `childArr` is your child. Try with just `childArr.map( ... )`. – Peppermintology Sep 12 '22 at 17:00
  • Does this answer your question? [Typescript Error: TS2339:Property 'map' does not exist on type 'string'](https://stackoverflow.com/questions/52337944/typescript-error-ts2339property-map-does-not-exist-on-type-string) – Connor Low Sep 12 '22 at 17:14
  • childArr returns an array of children, for each child in childArr how do I loop over its children? I expect 3 HTML elements, not 4 – Bill Sep 12 '22 at 17:14
  • Children.toArray(children); ensures its an array – Bill Sep 12 '22 at 17:16
  • Typescript Error: TS2339:Property 'map' does not exist on type 'string' does not answer this question, but thanks for the contribution – Bill Sep 12 '22 at 17:18
  • Sorry, my goof. As @Peppermintology pointed out, `childArr` might be an array type, but `childArr[0]` is a single object (`ReactElement`). The error is correct given the interface `PropsShape`'s definition of `children`. Either your interface is wrong, or you meant to do `childArr.map` instead of `childArr[0].map`, etc. – Connor Low Sep 12 '22 at 17:19
  • added `attempt 2` to try and show the problem with comments – Bill Sep 12 '22 at 17:25

1 Answers1

1

You can't do that (natively, without casting or ignoring errors, etc.), for a few reasons.

(Try here for an alternative).

1. children can be a ReactElement

Consider this:

<SideDataGridItem><div /></SideDataGridItem>

This is a valid usage of your component according to the interface for SideDataGridItem's props which allow for ReactElement, but the implementation breaks:

const SideDataGridItem = ({ children }: PropsShape): ReactElement => {
//                          ^ <div />
  const childArr = Children.toArray(children);
//      ^ [ <div /> ]
  return (
    <>
      {childArr[0] &&
//              ^ <div /> 
        childArr[0].map(function (item: any, i: number) {
{/*     ^^^^^^^^^^^ Error: "map" is not a property of a div!

TypeScript doesn't know for certain that the children of childArr have children of their own (which is likely solvable, but you have bigger problems).

2. ReactElement[] is not declared how you think it is

TypeScript errors for the following:

const foo: ReactElement[] = ( 
  <div>
    <span></span>
    <span></span>
  </div>
);

Even though div can contain any number of children, it is not an array-like type. For example, you couldn't do:

  const bar = <ul><li></li></ul>;
  bar[0]; 
//^^^^^^ error
// Element implicitly has an 'any' type because expression of type '0' can't // be used to index type 'Element'.
//  Property '0' does not exist on type 'Element'

What TypeScript actually expects for ReactElement[] is something like:

const bar: ReactElement[] = [
  <div>Hello</div>,
  <div>There</div>
];

It's worth mentioning that all but the above examples are instances of ReactElement and not ReactElement[]. This also includes your question's example:

const example: ReactElement = <>
   <div id='top'>
      <div>A1</div>
      <div>B1</div>
      <div>C1</div>
   </div>
   <div id='bottom'>
       <div>A2-</div>
       <div>B2-</div>
       <div>C2-</div>
    </div>
</>;

3. The JSX.Element interface is very limited

The underlying type for every JSX expression is JSX.Element: this is the API TypeScript provides for typing JSX implementations (like React). Note that JSX.Element is not generic, and that is the main issue preventing you from doing what you want here.

This requires a better understanding of how JSX works in TypeScript, but the short version is: TypeScript doesn't track the children of JSX elements because the resources needed to track that greatly decrease performance (see here and here).

Connor Low
  • 5,900
  • 3
  • 31
  • 52