matan
matan

Reputation: 107

Create Complex & Dynamic rowspan in React

I try to make function that get an array of object and according to object in the array generate a table with dynamic rowspan. I tried many solutions but none of them helped.

I tried this code,but I did not continue it because the beginning did not work well

 const returnTabel = state => {
return state.map((item, index) => {
  return (
    <tr key={index}>
      {Object.keys(item).map((key, index) => {
        if (Array.isArray(item[key])) {
          return item[key].map((object, index) => {
            return Object.keys(object).map((i, index) => {
              if (Array.isArray(object[i])) {
              } else {
                return (
                  <tr>
                    <td>{object[i]}</td>
                  </tr>
                );
              }
            });
          });
        } else {
          return (
            <td rowSpan={2} key={index}>
              {item[key]}
            </td>
          );
        }
      })}
    </tr>
  );
});};

Here is my data:

 const state = [
{
  name: 'Bill',
  info: [
    {
      hobby: 'Practice',
      field: [
        { type: 'Swim', hours: '6' },
        { type: 'Run', hours: '7' }
      ]
    },
    {
      hobby: 'Listen to music',
      field: [
        { type: 'Jazz', hours: '3' },
        { type: 'Electronic music', hours: '3' },
        { type: 'Hip hop', hours: '3' }
      ]
    }
  ],
  student: 'No'
},
{
  name: 'John',
  info: [
    {
      hobby: 'Practice',
      field: [
        { type: 'Swim', hours: '1' },
        { type: 'Run', hours: '2' }
      ]
    }
  ],
  student: 'Yes'
}]

I want to make this table with my data

enter image description here

Upvotes: 0

Views: 2154

Answers (1)

charlietfl
charlietfl

Reputation: 171700

You can simplify the render mapping if you map the data to rows that look like:

[{"txt":"Bill","rowSpan":5},{"txt":"Practice","rowSpan":2},{"txt":"Swim"},{"txt":"6"},{"txt":"No","rowSpan":5}]
//OR
[null,{"txt":"Listen to music","rowSpan":3},{"txt":"Jazz"},{"txt":"3"},null]
//OR
[null,null,{"txt":"Run"},{"txt":"7"},null]

Then the render simplifies down to:

  return (
    <table border="1">
      {rows.map(cells =>  (
          <tr>
            {cells.map(cell => cell && <td rowSpan={cell.rowSpan}>{cell.txt}</td>)}
          </tr>
        )
      )}
    </table>
  );

Working example

const data=[{name:"Bill",info:[{hobby:"Practice",field:[{type:"Swim",hours:"6"},{type:"Run",hours:"7"}]},{hobby:"Listen to music",field:[{type:"Jazz",hours:"3"},{type:"Electronic music",hours:"3"},{type:"Hip hop",hours:"3"}]}],student:"No"},{name:"John",info:[{hobby:"Practice",field:[{type:"Swim",hours:"1"},{type:"Run",hours:"2"}]}],student:"Yes"}];

const rowData = data.reduce((a, { name, info, student }) => {
  const rowSpan = info.reduce((a, { field }) => a + field.length, 0);

  let [firstCell, lastCell] = [name, student].map(txt => ({ txt, rowSpan }));

  info.forEach(({ hobby, field }, i) => {
    const rowSpan = field.length;

    let hobCell = { txt: hobby, rowSpan };

    field.forEach((f, j) => {
      const fieldCells = Object.values(f).map(txt => ({ txt }));

      if (j > 0) {
        hobCell = firstCell = lastCell = null;
      }
      const row = [firstCell, hobCell, ...fieldCells, lastCell];
      a.push(row);
    });
  });

  return a;
}, []);

console.log( rowData)

const Table = () => {
  const [rows] = React.useState(rowData);

  return (
    <table border="1">
      {rows.map((cells,i) =>  (
          <tr key={i}>
            {cells.map((cell,j) => cell && <td key={`${i}-${j}`} rowSpan={cell.rowSpan}>{cell.txt}</td>)}
          </tr>
        )
      )}
    </table>
  );
};
// Render it
ReactDOM.render(
  <Table />,
  document.getElementById("react")
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js"></script>
<div id="react"></div>

Upvotes: 1

Related Questions