Leo Messi
Leo Messi

Reputation: 6176

Make a React table sortable alphabetically

There is a component, GenericTable that receives as attributes the data.

in the component which has the data:

const headers = ['ID', 'Name', 'City'];
const rows = [{cells: ['1', 'John', 'Paris']},
              {cells: ['3', 'Ben', 'Berlin']},
              {cells: ['2', 'Helen', 'Barcelona']}
              ];
const idList = ['1', '3', '2'];

<GenericTable
  headers={headers}
  rows={rows}
  idList={idList}
/>

in GenericTable:

import { Table } from 'semantic-ui-react';

export default class GenericTable extends React.PureComponent {
  constructor(props) {
    super(props);
  }

  render() {
    const { headers, rows, idList } = this.props;
    
    return (
      <Table>
        <Table.Header>
          <Table.Row>
             {headers.map(header => (
                <Table.HeaderCell key={headers.indexOf(header)}>
                 {header}
                </Table.HeaderCell>
             )}
          </Table.Row>
        </Table.Header>

        <Table.Body>
          {rows.map((row, rowIndex) => (
            <Table.Row key={idList && idList[rowIndex]}>
                <Table.Cell>
                  ...
                </Table.Cell>
         
            </Table.Row>
          )}
        </Table.Body>
      </Table>
    );
  }
}

Is there a way to make the table sortable? For example, add a button near name header and when it is clicked to sort the table alphabetically based on that column?

I've tried to solve it but it doesn't work. I added the default value in the state, a button for each column header which can be clicked, and when it's clicked it should sort the data based on that column:

  import { Table } from 'semantic-ui-react';

export default class GenericTable extends React.PureComponent {
  constructor(props) {
    super(props);

    this.state = { // added state
      currentSort: 'default',
    };
  }

onSortChange = () => { // added method
  const { currentSort } = this.state;
  let nextSort;

  if (currentSort === 'down') nextSort = 'up';
  else if (currentSort === 'up') nextSort = 'default';
  else if (currentSort === 'default') nextSort = 'down';

  this.setState({
    currentSort: nextSort,
  });
};

  render() {

    const { currentSort } = this.state; // added state
    const sortTypes = { // added constant
      up: {
       class: 'sort-up',
       fn: (a, b) => a.name - b.name,
      },
      down: {
        class: 'sort-down',
        fn: (a, b) => b.name - a.name,
      },
     default: {
       class: 'sort',
       fn: (a, b) => a,
     },
   };
    const { headers, rows, idList } = this.props;
    
    return (
      <Table>
        <Table.Header>
          <Table.Row>
             {headers.map(header => (
                <Table.HeaderCell key={headers.indexOf(header)}>
                 {header}
                 // added button
                 <button onClick={this.onSortChange} type="button">
                   <i className={`fas fa-${sortTypes[currentSort].class}`} />
                 </button>
                </Table.HeaderCell>
             )}
          </Table.Row>
        </Table.Header>

        <Table.Body>
         // added below
          {[...rows].sort(sortTypes[currentSort].fn).map((row, rowIndex) => (
            <Table.Row key={idList && idList[rowIndex]}>
                <Table.Cell>
                  ...
                </Table.Cell>
         
            </Table.Row>
          )}
        </Table.Body>
      </Table>
    );
  }
}

Upvotes: 1

Views: 3809

Answers (2)

Stevetro
Stevetro

Reputation: 1963

You can safe the rows prop in the component state. If the table header is clicked you can update the state with the onSortChange function

<Table.Row>
      {headers.map((header,index) => (
            <Table.HeaderCell key={headers.indexOf(header)}>
             {header}
             // added below
             <button onClick={() => this.onSortChange(index)} type="button">
          <i className={`fas fa-${sortTypes[currentSort].class}`} />
             </button>
            </Table.HeaderCell>
         )}
</Table.Row>

onSortChange = (i) => { // added method
  var newRows = rows.sort(function(a, b){
    if(a.cells[i] < b.cells[i]) { return -1; }
    if(a.cells[i] > b.cells[i]) { return 1; }
    return 0;
  })
  this.setState({rows: newRows})
};

/Edit I did an example here

https://codesandbox.io/s/nervous-elbakyan-g9tw5?fontsize=14&hidenavigation=1&theme=dark

Upvotes: 1

Jasper Kinoti
Jasper Kinoti

Reputation: 499

Here is an example of how you can implement sorting using lodash. Note am using Hooks although I see you are still using classes but you should still be able to use the code either way. Just need to use component state instead of the useReducer hook. You can also use vanilla JS instead of lodash.

import _ from 'lodash'
import React from 'react'
import { Table } from 'semantic-ui-react'

const headers = ['ID', 'Name', 'City'];

const rows = [{cells: ['1', 'John', 'Paris']},
              {cells: ['3', 'Ben', 'Berlin']},
              {cells: ['2', 'Helen', 'Barcelona']}
              ];

const columnsData = rows.map(row => { 
  return {ID: row.cells[0], Name: row.cells[1], City: row.cells[2] }
})

function tableReducer(state, action) {
  switch (action.type) {
    case 'CHANGE_SORT':
      if (state.column === action.column) {
        return {
          ...state,
          data: state.data.reverse(),
          direction:
            state.direction === 'ascending' ? 'descending' : 'ascending',
        }
      }

      return {
        column: action.column,
        data: _.sortBy(state.data, [action.column]),
        direction: 'ascending',
      }
    default:
      throw new Error()
  }
}


function TableExampleSortable() {
  const [state, dispatch] = React.useReducer(tableReducer, {
    column: null,
    data: columnsData,
    direction: null,
  })
  const { column, data, direction } = state

  return (
    <Table>
        <Table.Header>
          <Table.Row>
             {headers.map(header => (
                <Table.HeaderCell key={headers.indexOf(header)}
                sorted={column === header ? direction : null}
                onClick={() => dispatch({ type: 'CHANGE_SORT', column: header })}>
                 {header}
                </Table.HeaderCell>
             ))}
          </Table.Row>
        </Table.Header>

        <Table.Body>
        {data.map(({ ID, Name, City }) => (
          <Table.Row key={ID}>
            <Table.Cell>{ID}</Table.Cell>
            <Table.Cell>{Name}</Table.Cell>
            <Table.Cell>{City}</Table.Cell>
          </Table.Row>
        ))}
      </Table.Body>

      </Table>
  )
}

export default TableExampleSortable

Upvotes: 0

Related Questions