2
votes

I'm having problems dynamically adding jsx table rows to a table in react.

The case I have is the following:

If a table row contains sub rows, add them in addition to the main row.

This was my initial design:

        {rows.map((row) => {
        const expanded = _.includes(expandedRows, row.name); // check if row is expanded or not
        const subRows = row.subRows && row.subRows.map((subRow) =>
          <SubRow key={subRow.name} theme={theme} subRow={subRow.name} />);

        return (
          <div>
            <Row
              key={row.name}
              theme={theme}
              handleUpdate={handleUpdate}
              handleExpandOrCollapseRow={handleExpandOrCollapseRow}
            />
            {expanded && row.subRows && subRows}
            {expanded && !row.subRows && <p>No subrows exist</p>}
          </div>
        );
      })

The problem is that div is not allowed as child in the tbody tag. Any ideas how I can get around this without wrapping my return in a div?

I was thinking to move the whole tbody to my Row component and conditionally render the subrows there, but I can't see how it would help since I still can't use any wrapper around the return... the only wrapper I can use is tbody and that can only occur once as parent to all rows.

Any idea?

1
why don't you wrap your code with a tbody instead of a div and remove the tbody from the parent ? - Olivier Boissé
@oliv37 because then it would render a tbody for each loop. i.e. each tr (and possibly subrows) would be wrapped with tbody. - Willy G
I give you a possible solution, for each step of the loop (in the map function) instead of returning a Component, you return an array of Component containing a Row and maybe some subrows. As a result you get an array of array, you can then flatten this result to display an array of components - Olivier Boissé
Yeah, I would recommend @oliv37 approach. In fact, I've added an answer using this method, as I do the same in my react projects. - dschu
This is maybe simpler to use a for loop instead of a map. In the for loop you can do result.add(<Row ...>) then result.add(subrow) for each subrows - Olivier Boissé

1 Answers

2
votes

Instead returning a wrapped div around multipls tr's, you can simple collect the rows in an array and then return the array afterwards.

Here's an example

renderTableRows() {
    const rows = [];

    // Push the main row first
    rows.push(
        <Row
            key={row.name}
            theme={theme}
            handleUpdate={handleUpdate}
            handleExpandOrCollapseRow={handleExpandOrCollapseRow}
        />
    );

    // Then push the subrows
    row.subRows.forEach(subRow =>
        rows.push(
            <SubRow key={subRow.name} theme={theme} subRow={subRow.name} />
        );
    );

    return rows;
}

Then add the renderTableRows() method to your view like this

render() {
    if( this.state.isLoading ) {
        return Table.renderSpinner();
    }

    return (
        <table>
            { this.renderTableRows() }
        </table>
    );
}

I can't say for sure if that code will work for you out of the box. But this pattern should help you to solve your problem.