1
\$\begingroup\$

I have a React component that generates a table based on an array of objects, which gets populated with data in an object.

I'm trying to learn as much as I can about React.js / JavaScript. Could anyone with more experience in this area show me a way to simplify the code that I have here? Or if there are any other 'fancy' ways of doing what is being done in the code?

App.js

import React, { Component } from 'react';
import Table from './Table';

//Columns defines table headings and properties to be placed into the body
let columns = [
  {
    heading: 'Name',
    property: 'name'
  },
  {
    heading: 'Age',
    property: 'age'
  },
  {
    heading: 'Sex',
    property: 'sex'
  },
  {
    heading: 'Breed',
    property: 'breed'
  },
]

//Data is the array of objects to be placed into the table
let data = [
  {
    name: 'Sabrina',
    age: '6',
    sex: 'Female',
    breed: 'Staffordshire'
  },
  {
    name: 'Max',
    age: '2',
    sex: 'Male',
    breed: 'Boxer'
  }
]

class App extends Component {

  render() {
    return (
      <>
        <Table
          columns={columns}
          data={data}
          propertyAsKey='name' //The data property to be used as a key
        />
      </>
    );
  }
}

export default App;

Table.js

import React, { Component } from 'react';

class Table extends Component {

  buildTable = (columns, data, key) => {
    let headerRow = [];
    let dataRows = [];

    //Build the table header
    columns.forEach (col => {
      headerRow.push(
        <th key={`header-${col.heading}`}>{col.heading}</th>
      );
    });

    //Build the rows
    data.forEach(item => {
      let dataCells = [];

      //Build cells for this row
      columns.forEach (col => {
        dataCells.push(
          <td key={`${item[key]}-${col.property}`}>{item[col.property]}</td>
        );
      });

      //Push out row
      dataRows.push(
        <tr key={`${item[key]}-row`}>{dataCells}</tr>
      )
    });

    return(
      <>
        <thead>
          <tr>{headerRow}</tr>
        </thead>
        <tbody>
          {dataRows}
        </tbody>
      </>
    );
  };

  render() {
    const {
      columns,
      data,
      propertyAsKey
    } = this.props;

    return (
      <table className='table'>
        {this.buildTable(columns, data, propertyAsKey)}
      </table>
    );
  }
}

export default Table;
\$\endgroup\$
1
\$\begingroup\$

TL;DR Fully functionnal example :

//Columns defines table headings and properties to be placed into the body
const columns = [{ heading: 'Name', property: 'name' }, { heading: 'Age', property: 'age' }, { heading: 'Sex', property: 'sex' }, { heading: 'Breed', property: 'breed' },]

//Data is the array of objects to be placed into the table
const data = [{ name: 'Sabrina', age: '6', sex: 'Female', breed: 'Staffordshire' }, { name: 'Max', age: '2', sex: 'Male', breed: 'Boxer' }]

const App = props => <Table columns={columns} data={data} propertyAsKey='name' />

const Table = ({ columns, data, propertyAsKey }) => 
    <table className='table'>
        <thead>
            <tr>{columns.map(col => <th key={`header-${col.heading}`}>{col.heading}</th>)}</tr>
        </thead>
        <tbody>
            {data.map(item =>
                <tr key={`${item[propertyAsKey]}-row`}>
                    {columns.map(col => <td key={`${item[propertyAsKey]}-${col.property}`}>{item[col.property]}</td>)}
                </tr>
            )}
        </tbody>
    </table>
    
ReactDOM.render(<App/>, document.getElementById('root'))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.1/umd/react-dom.production.min.js"></script>
<div id='root'>


The main thing that would highly reduce this code is the map function. This function can be applied on an array, execute a callback on every item in the array and returns a new array.

By using it you can reduce the following code :

let headerRow = [];

columns.forEach(col => {
    headerRow.push(
        <th key={`header-${col.heading}`}>{col.heading}</th>
    );
});

Can be reduced to this with the exact same result :

const headerRow = columns.map(col => <th key={`header-${col.heading}`}>{col.heading}</th>)

You can now include this function directly into your JSX :

<thead>
    <tr>{columns.map(col => <th key={`header-${col.heading}`}>{col.heading}</th>)}</tr>
</thead>

And the same thing with nested mapping for the body :

<tbody>
    {data.map(item => 
        <tr key={`${item[key]}-row`}>
            {columns.map(col => <td key={`${item[key]}-${col.property}`}>{item[col.property]}</td>)}
        </tr>
    )}
</tbody>

Your full buildTable function is now reduced to the following :

buildTable = (columns, data, key) => {
    return (
        <>
            <thead>
                <tr>{columns.map(col => <th key={`header-${col.heading}`}>{col.heading}</th>)}</tr>
            </thead>
            <tbody>
                {data.map(item => 
                    <tr key={`${item[key]}-row`}>
                        {columns.map(col => <td key={`${item[key]}-${col.property}`}>{item[col.property]}</td>)}
                    </tr>
                )}
            </tbody>
        </>
    );
};

If you want to go a little further you can also delete this function and embed this code in your render function JSX. I will also convert your component into a stateless function, since you are not using any state value :

const Table = ({ columns, data, propertyAsKey }) => //Deconstructs your props
    <table className='table'>
        <thead>
            <tr>{columns.map(col => <th key={`header-${col.heading}`}>{col.heading}</th>)}</tr>
        </thead>
        <tbody>
            {data.map(item =>
                <tr key={`${item[propertyAsKey]}-row`}>
                    {columns.map(col => <td key={`${item[propertyAsKey]}-${col.property}`}>{item[col.property]}</td>)}
                </tr>
            )}
        </tbody>
    </table>

(You may add brackets and a return statement after the arrow sign, its up to you)

It is also not required to have a fragment as a parent element in your render, as long as there is a single element, it is fine :

class App extends React.Component {
  render() {
    return (
        <Table
          columns={columns}
          data={data}
          propertyAsKey='name' //The data property to be used as a key
        />
    );
  }
}

Your App component could also be converted to a stateless component:

const App = props => <Table columns={columns} data={data} propertyAsKey='name' />
\$\endgroup\$
  • 1
    \$\begingroup\$ This is perfect, thank you for showing me. \$\endgroup\$ – Nate Thompson Feb 2 at 3:47

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.