Home > Software engineering >  JavaScript rendering object containing arrays as table
JavaScript rendering object containing arrays as table

Time:07-22

I have an object (equipmentTable) containing arrays of values for each column in a table. I am having trouble getting this table to render properly. I think I am pretty close.

Here is the object:

{
    "manufacturer": [
        "Google",
        "Apple"
    ],
    "modelNumber": [
        "123456",
        "36987"
    ],
    "serialNumber": [
        "889977",
        "558877"
    ]
}

And what I have tried:

{equipmentTable && 
                <table className="def-tbl">
                    <thead>
                        <th>Manufacturer</th>
                        <th>Model Number</th>
                        <th>Serial Number</th>
                    </thead>
                    <tbody>
                        {console.log(equipmentTable)}
                        {equipmentTable.manufacturer.map((value) => (
                            <tr>
                                <td>
                                    {value}
                                </td>
                            </tr>
                        ))}
                        {equipmentTable.serialNumber.map((value) => (
                            <tr>
                                <td>
                                    {value}
                                </td>
                            </tr>
                        ))}
                    </tbody>
                </table>}

No matter what I try, everything renders in the first column.

Any help is appreciated!

CodePudding user response:

It's a little unclear the output you want, but the main problem is that you are defining a new row on each iteration for each property. Moving the <tr> elements outside of the map() calls would be the logical step if your data was shaped per row.

But given the structure of your data it looks like you'll want to access the columns by index in a single map.

{
  equipmentTable && (
    <table className='def-tbl'>
      <thead>
        <th>Manufacturer</th>
        <th>Model Number</th>
        <th>Serial Number</th>
      </thead>
      <tbody>
        {equipmentTable.manufacturer.map((_, i) => (
          <tr key={`row_${i}`}>
            <td>{equipmentTable.manufacturer[i]}</td>
            <td>{equipmentTable.modelNumber[i]}</td>
            <td>{equipmentTable.serialNumber[i]}</td>
          </tr>
        ))}
      </tbody>
    </table>
  );
}

Or, to make it dynamic you could use nested map() calls on the Object.keys() and Object.values() of your data for the headers and body accordingly.

{
  equipmentTable && (
    <table className='def-tbl'>
      <thead>
        {Object.keys(equipmentTable).map((header) => (
          <th key={header}>{header}</th>
        ))}
      </thead>
      <tbody>
        {Object.values(equipmentTable)[0].map((_, i) => (
          <tr key={`row${i}`}>
            {Object.values(equipmentTable).map((values, j) => (
              <td key={`row${i}_col${j}`}>{values[i]}</td>
            ))}
          </tr>
        ))}
      </tbody>
    </table>
  );
}

CodePudding user response:

Your data structure is a bit odd for representing a table. I recommend you transform the dataset into a matrix for easy mapping to table rows:

const toDataRows = (map) =>
  (keys => map[keys[0]].map((_, i) =>
    keys.map(k => map[k][i])))
  (Object.keys(map));

First gather the keys of the map. Keep in mind, the order will not be guaranteed to match the order of the columns in the HTML. Now map the first item (manufacture array) in the map and locate the corresponding data item at the key and index.

Note: Don't forget the <tr> inside the <thead>.

Working Demo

const { useState } = React;

const toDataRows = (map) =>
  (keys => map[keys[0]].map((_, i) =>
    keys.map(k => map[k][i])))
  (Object.keys(map));

const equipmentTable = {
  "manufacturer" : [ "Google" , "Apple"  ],
  "modelNumber"  : [ "123456" , "36987"  ],
  "serialNumber" : [ "889977" , "558877" ]
};

const EquipmentTable = (props) => {
  const { data } = props;
  return (
    <table className="def-tbl">
      <thead>
        <tr>
          <th>Manufacturer</th>
          <th>Model Number</th>
          <th>Serial Number</th>
        </tr>
      </thead>
      <tbody>
        {data.map((row) => (
          <tr key={row[0]}>
            {row.map((item) => (
              <td key={item}>{item}</td>
            ))}
          </tr>
        ))}
    </tbody>
  </table>
  );
};

const App = () => (
  <div>
    <h1>Equipment App</h1>
    <EquipmentTable data={toDataRows(equipmentTable)} />
  </div>
);

ReactDOM.createRoot(document.querySelector('#root')).render(<App />);
.def-tbl { border-collapse: collapse }
table, th, td { border: thin solid grey; }
th, td { padding: 0.25em; }
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.1.0/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.1.0/umd/react-dom.development.js"></script>


Advanced solution

In the example below, the original data is transformed into a list of object called "records". An array of column definitions i.e. columnDefs are also passed into the Table. We only need these two things to render a complete table.

const { useState } = React;

const toRecords = (map) =>
  (keys => map[keys[0]].map((_, i) =>
    keys.reduce((a, k) =>
      ({ ...a, [k]: map[k][i] }), {})))
  (Object.keys(map));

const Table = (props) => {
  const { columnDefs, dataSet } = props;
  return (
    <table className="def-tbl">
      <thead>
        <tr>
          {columnDefs.map(({ field, headerName }) => (
            <th data-field={field} key={field}>
              {headerName}
            </th>
          ))}
        </tr>
      </thead>
      <tbody>
        {dataSet.map((record, index) => (
          <tr key={record[columnDefs[0].field]}>
            {columnDefs.map(({ field }) => (
              <td key={record[field]}>
                {record[field]}
              </td>
            ))}
          </tr>
        ))}
      </tbody>
    </table>
  );
};

const equipmentData = {
  "manufacturer" : [ "Google" , "Apple"  ],
  "modelNumber"  : [ "123456" , "36987"  ],
  "serialNumber" : [ "889977" , "558877" ]
};

const columnDefs = [
  { field: 'manufacturer' , headerName: 'Manufacturer' },
  { field: 'modelNumber'  , headerName: 'Model Number' },
  { field: 'serialNumber' , headerName: 'Serial Number' },
]

const App = () => (
  <div>
    <h1>Equipment App</h1>
    <Table
      columnDefs={columnDefs}
      dataSet={toRecords(equipmentData)}
    />
  </div>
);

ReactDOM.createRoot(document.querySelector('#root')).render(<App />);
.def-tbl { border-collapse: collapse }
table, th, td { border: thin solid grey; }
th, td { padding: 0.25em; }
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.1.0/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.1.0/umd/react-dom.development.js"></script>

  • Related