Search code examples
reactjsstyled-componentschildren

How to write exclusion for props' children when mapping over them in a component?


Working on my D.R.Y. I'm trying to pass down a parent's data to a child component so I can re-use the child component. Given the parent component I have:

<Child data="data">
  <svg viewBox={'0 0 500 500'}>
    <path d="path" />
  </svg>
  <p>This is some text</p>
</Child>

Child.js:

import React from 'react'
import Foo from '../Foo'
import { Container, Svg, Div, Text } from './ChildElements'

const Child = props => {
  return (
    <>
      <Container>
        {props.children.map((c, k) => {
          if (c.type === 'svg')
            return (
              <Svg key={k} viewBox={c.props.viewBox}>
                {c.props.children}
              </Svg>
            )
        })}
        <Div>
          {props.children.map((c, k) => {
            if (c.type === 'p') return <Text key={k}>{c.children}</Text>
          })}
          <Foo bar={props.data} />
        </Div>
      </Container>
    </>
  )
}
export default Child

child.js hardcoded:

import React from 'react'
import Foo from '../Foo'
import { Container, Svg, Div, Text } from './ChildElements'

const Child = ({data}) => {
  return (
    <>
      <Container>
        <Svg viewBox={'0 0 500 500'}><path d="path" /></Svg>
        <Div>
          <Text>Hard coded text</Text>
          <Foo bar={data} />
        </Div>
      </Container>
    </>
  )
}

export default Child

The child component works but if I exclude Text (<p>This is some text</p>) from Parent the app throws an error of:

TypeError: props.children.map is not a function

and in the terminal I get an ESLint error:

Array.prototype.map() expects a value to be returned at the end of arrow function

How can I condition for Svg and Text when passing to the child component if I don't know what will be included in the Parent?

Research:


Solution

  • This is what React.Children.map was created for. When multiple children are provided, props.children will be an array, and your code works. When only one or no children are provided, however, props.children will not be an array, causing your code to need to account for every variation of none, one, or many children. React.Children.map works like the normal array map, but handles those three cases gracefully.

    You probably also want to look into React.cloneElement to handle your element creation code, though in your case I think you just want to filter for some of the elements, so you can just return them.

        {React.Children.map(props.children, (c, k) => {
          if (c.type === 'svg')
            return c
        })}
    

    There is also a React.Children.toArray, which would allow you to use filter if you wanted to.

    And finally, I should note that using an index as a key is pointless. React already uses the order to identify children. If you double up on that functionality by providing an index as a key, it won't change the behavior. An key should be stored in the data which is used to generate the Svg element, so that Svg elements which represent the same data are correctly identified.