Home > Mobile >  React map over the children of the child
React map over the children of the child

Time:09-13

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'.

CodePudding user response:

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).

  • Related