Search code examples
javascriptreactjsjavascript-objects

Rendering and Grouping using React Tables


I am creating a dynamic table using react based on objects.

The problem is that I am trying to render a new row for every three TD, but instead it is rendering all TDs in the same table row (tr).

How can I separate the TDs into az new TR after a group of 3 TDs have been filled?

Here is my code:

Component rendering table:

`export class Roster extends React.Component{

render(){
    return(
        <div id="tab">
            <table >
                <tbody>
                    <tr>
                        <td colSpan={3}>
                            <h4>Goalkeepers</h4>
                        </td>
                    </tr>
                </tbody>

                <Goalies/>
        </div>
    )
}

}`

Component looping through object:

`class Goalies extends React.Component{

getTable() {
    let td_array = [];
    let goal_object = {};

    Object.keys(goalies).forEach(function(key) {
        goal_object = (goalies[key]);
      });

    for(const checker in goal_object){
        td_array.push(<td key={checker}>{goal_object[checker].p_name} <br/> {goal_object[checker].city} <br/> {goal_object[checker].number}</td>);
    }

    return(
        <tr>
            {td_array}
        </tr>
    )
}

  render() {
    return (<tbody>{this.getTable()}</tbody>)
  }

}`

Objects:

export const def = { 1:{ p_name:"p1", number: 2, city: "city1" },

2:{
    p_name:"p2",
    number: 5,
    city: "city2"
},

3:{
    p_name:"p3",
    number: 3,
    city: "city3"
},

4:{
    p_name:"p4",
    number: 7,
    city: "city4"
},

5:{
    p_name:"p5",
    number: 15,
    city: "city5"
},

6:{
    p_name:"p6",
    number: 21,
    city: "city6"
}

}

I want to get:

td1 td2 td3
td4 td5 td6

instead, I am getting:

td1 td2 td3 td4 td5 td6

I tried using conditional statements, pushing into a new array...


Solution

  • In order to achieve the layout you're after, the correct HTML markup would look like this:

    <table>
      <thead>
        <tr>
          <th colspan="3">
            <h4>Goalkeepers</h4>
          </th>
        </tr>
      </thead>
      <tbody>
        <tr>
          <td>td1</td>
          <td>td2</td>
          <td>td3</td>
        </tr>
        <tr>
          <td>td4</td>
          <td>td5</td>
          <td>td6</td>
        </tr>
      </tbody>
    </table>
    

    First, you'll want to group your items into arrays of n (3 in your case). I'd use a generator function:

    function* toChunks(arr, n) {
      for (let i = 0; i < arr.length; i += n) {
        yield arr.slice(i, i + n)
      }
    }
    const rows = [...toChunks(items, 3)]
    

    As an alternative, you could also use:

    const chunk = (arr, n) =>
      [...Array.from({ length: Math.ceil(arr.length / n) }).keys()].map((key) =>
        arr.slice(key * n, (key + 1) * n)
      )
    const rows = chunk(items, 3)
    

    Given the above rows, your component would look like this:

    const Goalies = ({ rows }) => (
      <tbody>
        {rows.map((row, rk) => (
          <tr key={rk}>
            {row.map((cell, ck) => (
              <td key={ck}>{cell.number}</td>
            ))}
          </tr>
        ))}
      </tbody>
    )
    

    Demo here


    However, by looking at your data I think you shouldn't be using a <table> here at all. The layout you're after could easier be achieved with:

    const Cells = ({ items }) => (
      <div className="myWrapper">
        {items.map((item, key) => (
          <div key={key}>{item.number}</div>
        ))}
      </div>
    )
    

    along with any of the following CSS models:

    .myWrapper {
      display: flex;
      flex-wrap: wrap;
    }
    .myWrapper > * {
      width: calc(100%/3)
    }
    
    /* or alternatively: */
    .myWrapper {
      display: grid;
      grid-template-columns: repeat(3, minmax(0, 1fr))
    }