1

In a React class component, I'm writing a method that builds a <select> dropdown and Babel is giving an error. The error is caused by the first <select> tag not being closed. I can't figure out the correct react-jsx syntax for this.

In the code below, I'm trying to encapsulate the code that creates the <select> into another method in the component class. If I have the function just build the options and put the <select> and </select> around that function (up in the render method) it works, but I want to put the selected value into the <select> tag instead of using the if statement, plus keep it all together.

What's the proper syntax for this?

Thanks...

The code with comments pointing to the problem line:

 render()
 {

 function buildQtyOptions(isAvailable, qty)
 {
    var opts = [];

    if(!isAvailable)
    {
      opts.push(<td></td>);
      return opts;
    }

    {/* Uncommenting this next line causes the error because the select is unclosed */}
    {/* opts.push(<select>) */}

    for (var i=1; i < 11; i++)
    {
       if(i === parseInt(qty))
       {
          opts.push(<option value={i} selected>{i}</option>);
       }
       else
       {
          opts.push(<option value={i}>{i}</option>);
       }
    }

    {/* opts.push(</select>); */}

    return opts;
 }

 {/*  Other methods omitted here */}

  return(
  <tr>
     <td>{getImage(this.props.available)}</td>
     <td>{this.props.name}</td>
     <td>
       {/* I want to move this and the closing select into the buildQtyOptions method */}
       {/*<select> */}
          { buildQtyOptions(this.props.available, this.props.years) }
       {/*</select> */}
     </td>

  </tr>); 
Jim Archer
  • 1,337
  • 5
  • 30
  • 43

2 Answers2

3

JSX is not really markup, it's syntactic sugar for function calls.

<select></select>

becomes

React.createElement('select');

Maybe this helps to understand why you cannot really separate opening and closing "tags". You could consider them to be equivalent to parenthesis I guess.

Instead you can do

return <select>{opts}</select>;

(which translates to return React.createElement('select', null, opts);).

Felix Kling
  • 795,719
  • 175
  • 1,089
  • 1,143
  • Two excellent answers, I wish I could accept them both. Thank you Felix and Pedro. I should have seen that myself. I knew that Babel made that translation but the impact of it escaped me. I'll read more on React.createElement(); – Jim Archer May 03 '17 at 17:27
0

Try doing it all at once using map instead of using a for-loop, like this:

opts.push(
  <select>
    { [...Array(10).keys()].map(x => x + 1).map((i) => {
        if (i == parseInt(qty)) {
          return <option value={i} key={i} selected>{i}</option>;
        } else {
          return <option value={i} key={i}>{i}</option>;
        }
      });
    }
  </select>
);

The [...Array(10).keys()] trick to generate a range of numbers is from this answer. The first map simply offsets the range so that you get numbers from 1 to 10 instead of from 0 to 9, and the second one actually generates the <option> components. This works because it is valid to pass the children of your component all at once as an array.

The reason you can't do this the way you want is that each closed pair of JSX tags gets translated into a React.createElement call, so if you don't close the JSX tag the JSX compiler has no idea when to call React.createElement.

Community
  • 1
  • 1
Pedro Castilho
  • 10,174
  • 2
  • 28
  • 39