0

I am using ReactDOM.render() to render a component I have created. The component is fairly complicated, due to the specifics of the implementation, but I can easily render it iff I avoid using JSX syntax. However, if I use JSX, I cannot render the component at all and I get the following error:

TypeError: _this2.props.children.forEach is not a function

My code can be seen below (I also get a few warnings that I haven't gotten around to fixing yet, so you can just ignore those for the time being). Bear in mind that the structure of the HTML for the component is very strict (due to the CSS framework I'm using) and cannot be changed. Is there a way to use JSX for achieving the same result and, if so, what is it that I'm doing wrong?

// This function was based on this answer: https://stackoverflow.com/a/10734934/1650200
function generateUniqueId() {
  // always start with a letter (for DOM friendlyness)
  var idstr = String.fromCharCode(Math.floor((Math.random() * 25) + 65));
  do {
    // between numbers and characters (48 is 0 and 90 is Z (42-48 = 90)
    var ascicode = Math.floor((Math.random() * 42) + 48);
    if (ascicode < 58 || ascicode > 64) {
      // exclude all chars between : (58) and @ (64)
      idstr += String.fromCharCode(ascicode);
    }
  } while (idstr.length < 32);

  return (idstr);
}

// Technically this is not exactly a component, but I use it as such to make things simpler.
class Tab extends React.Component {
  render() {
    return React.createElement('div', {}, this.props.children);
  }
}

// This is my Tabs component
class Tabs extends React.Component {
  // In the constructor, I take all children passed to the component
  // and push them to state with the necessary changes made to them.
  constructor(props) {
    super(props);
    var state = {
      group: 'tab_group_' + generateUniqueId(),
      children: []
    }
    this.props.children.forEach(
      function(child) {
        if (!child instanceof Tab) {
          throw "All children of a 'Tabs' component need to be of type 'Tab'. Expected type: 'Tab' Found Type: '" + child.class + "'";
          return;
        }
        var tab = Object.assign({}, child);
        tab.internalId = 'tab_' + generateUniqueId();
        state.children.push(tab);
      }
    );
    this.state = state;
  }
  // When rendering, I don't render the children as needed, but I create
  // the structure I need to use for the final result.
  render() {
    var childrenToRender = [];
    var groupName = this.state.group;
    this.state.children.forEach(function(tab) {
      childrenToRender.push(
        React.createElement(
          'input', {
            type: 'radio',
            name: groupName,
            id: tab.internalId,
            checked: true,
            'aria-hidden': 'true'
          }
        )
      );
      childrenToRender.push(
        React.createElement(
          'label', {
            'htmlFor': tab.internalId,
            'aria-hidden': 'true'
          },
          'demo-tab'
        )
      );
      childrenToRender.push(React.createElement('div', {}, tab.props.children));
    });
    return React.createElement('div', {
      'className': 'tabs'
    }, childrenToRender);
  }
}

// This works fine
ReactDOM.render(
  React.createElement(Tabs, {}, [React.createElement(Tab, {}, 'Hello world')]),
  document.getElementById('root')
);

// This fails with the error mentioned above
// ReactDOM.render(
//  <Tabs>
//   <Tab>Hello, world!</Tab>
//  </Tabs>,
//  document.getElementById('root')
// );
<link rel="stylesheet" href="https://gitcdn.link/repo/Chalarangelo/mini.css/master/dist/mini-default.min.css">
<script src="https://unpkg.com/react@latest/dist/react.js"></script>
<script src="https://unpkg.com/react-dom@latest/dist/react-dom.js"></script>
<script src="https://unpkg.com/babel-standalone@6.15.0/babel.min.js"></script>
<div id="root"></div>

Update: This only happens if I actually pass only one <Tab> to the <Tabs> due to the way it's processed. If, for example, I use the following code, I can use JSX to render the component and its contents:

ReactDOM.render(
    <Tabs>
        <Tab>Hello, world!</Tab>
        <Tab>Hello, world!</Tab>
    </Tabs>,
    document.getElementById('root')
);
Angelos Chalaris
  • 6,611
  • 8
  • 49
  • 75
  • probably no forEach. What's the children anyway. Maybe this would help on how to access it. https://facebook.github.io/react/docs/jsx-in-depth.html Look for `Functions as children` – A. L May 30 '17 at 07:24

1 Answers1

0

After checking out what babel output from the JSX code, I realized that it was not ouputting something like [React.createElement(Tab, {}, 'Hello world')] but rather something more like React.createElement(Tab, {}, 'Hello world'), meaning it was not an array, thus causing problems with .forEach().

To anyone interested, what I did was check if this.props.children is an array and, if not, to actually turn it into one. Sample below:

if (!Array.isArray(this.props.children))
    var tempProps = [this.props.children];
else
    var tempProps = this.props.children;
tempProps.forEach(
    // Rest of the code is pretty much the same as before
);

This is not a very elegant solution, so feel free to post more elegant answers if you know any.

Angelos Chalaris
  • 6,611
  • 8
  • 49
  • 75