'Iterate over children, find last element

I work on breadcrumb component and I iterate over children like:

renderChildren = () => {
  const { children, divider } = this.props;
  return (
    React.Children.map(children, child => (
      <>
        {child}
        {divider}
      </>
    ))
  );
}

Divider is between elements in breadcrumb and on last element in breadcrumb I do not want to put divider. One picture, I do not want to display last chevron (divider):

enter image description here



Solution 1:[1]

renderChildren = () => {
  const { children, divider } = this.props;
  return (
    React.Children.map(children, (child, index) => (
      <>
        {child}
        {(index < children.length - 1) && divider}
      </>
    ))
  );
}

Update:

const children: React.ReactNode Object is possibly 'null' or 'undefined'.ts(2533)

Is out of scope of this answer, but to handle this scenario

renderChildren = () => {
  const { children, divider } = this.props;
  if (children) { // this fixes error above
    return (
      React.Children.map(children, (child, index) => (
        <>
          {child}
          {(index < children.length - 1) && divider}
        </>
      ))
    );
  }
  return null
}

That error comes because you defined your prop as children?: ReactNode, because it's optional TypeScript secures you from undefined values

Solution 2:[2]

You could use index and check if it equals to the length of the children array:

renderChildren = () => {
  const { children, divider } = this.props;
  return (
    React.Children.map(children, (child, index) => (
      <>
        {child}
        {index !== children.length-1 && divider}
      </>
    ))
  );
}

Solution 3:[3]

Using children.length is wrong and not typesafe; it simply 'happens to work', and may be broken in future versions of react. ReactNode can be null, which will break this even right now.

You should use the react top level api to process children;

  • use React.Children.toArray to determine the number of child elements.

  • use React.Children.map to map the child elements to new elements.

Like this:

export const Breadcrumb = (props: { divider: () => ReactNode, children: React.ReactNode }) => {
    const childCount = React.Children.toArray(props.children).length;
    return (<>
        {React.Children.map(props.children, (child, index) => {
            if (React.isValidElement(child)) {
                const isLastChild = index === childCount - 1;
                if (isLastChild) {
                    return React.cloneElement(child, {style: {border: "1px solid #ff0000"}})
                }
                return (<>
                    {child}
                    {props.divider()}
                </>);
            }
        })};
    </>);
}

eg.

<Breadcrumb divider={() => <span> &gt; </span>}>
  <span>A</span>
  <span>B</span>
  <span>C</span>
</Breadcrumb>

Solution 4:[4]

THIS WORKS FOR ME BETTER

  return Children.map(children, (child, index) => {
    const isLastChildrenIndex = index === Children.count(children) - 1;
    return (
      <>
        {child}
        {isLastChildrenIndex &&  <Divider />}
      </>
    );
  });

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2 Clarity
Solution 3 Doug
Solution 4 Omar Borji