0

I've managed to make the table being sorted by each of its columns by clicking on the button next to a column header.

However, I would expect it to sort it reverse alphabetically when it is clicked for the second time and to go back to initial state when it's clicked for the third time.

As it is now it just works for the first click - it sorts it alphabetically - but after that it doesn't do anything no matter how many times it is clicked.

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

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

    this.state = { 
      currentSort: 'default',
      rows: this.props.rows, // the original rows are saved in state
    };
  }

  onSortChange = index => {
    let nextSort;

    const newRows = this.props.rows.sort(function(a, b) {
      if (a.cells[index] < b.cells[index]) {
        nextSort = 'up';
        return -1;
      }
      if (a.cells[index] > b.cells[index]) {
        nextSort = 'default';
        return 1;
      }
      nextSort = 'down';
      return 0;
    });
    this.setState({ rows: newRows, 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(index)} type="button">
                   <i className={`fas fa-${sortTypes[currentSort].class}`} />
                 </button>
                </Table.HeaderCell>
             )}
          </Table.Row>
        </Table.Header>

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

I guess something is wrong in onSortChange but don't know what.

Leo Messi
  • 5,157
  • 14
  • 63
  • 125

2 Answers2

0

Javascript sorting maybe isn't as straight forward as it seems. I try to explain it to you so you can fix your function.

Let's assume we have these values:

var items = [
  { name: 'Edward', value: 21 },
  { name: 'Sharpe', value: 37 },
  { name: 'And', value: 45 },
  { name: 'The', value: -12 },
  { name: 'Magnetic', value: 13 },
  { name: 'Zeros', value: 37 }
];

And we write this function:

items.sort()

not much happens. This is, because we're dealing with a list of objects and therefore we want to add a compareFunction. You did this quite well but I assume you don't quite understand what it does. If we use this compareFunction for instance:

items.sort(function (a, b) {
  var nameA = a.name.toUpperCase(); // ignore upper/lower case!
  var nameB = b.name.toUpperCase(); // ignore upper/lower case!
  if (nameA < nameB) {
    return -1;
  }
  if (nameA > nameB) {
    return 1;
  }

  // Namen müssen gleich sein
  return 0;
});

Our list will be sorted alphabetically. Why? Because we're telling the function right here:

var nameA = a.name.toUpperCase(); // ignore upper/lower case!
var nameB = b.name.toUpperCase(); // ignore upper/lower case!

that we're looking for the "name" properties of our object. We could easily replace the a.name.toUpperCase() with a.value and we would now be sorting our array by the value inside the value property of our object.

This being said, your sort function is currently programmed to always sort alphabetically (a - z). You never implemented a logic to do the opposite or to stop sorting. I think you probably assumed that the compareFunction already does the sort-switch thing - but it doesn't.

To do a descending sort (z - a) you could use the .reverse() function. It will look something like this:

items.sort(function (a, b) {
  var nameA = a.name.toUpperCase(); // ignore upper/lower case!
  var nameB = b.name.toUpperCase(); // ignore upper/lower case!
  if (nameA < nameB) {
    return -1;
  }
  if (nameA > nameB) {
    return 1;
  }

  // Namen müssen gleich sein
  return 0;
}).reverse();

et voilà - your list is now sorted with a descending order (z - a). (read more here: Sorting strings in descending order in Javascript (Most efficiently)?).

All you'll need to do now is to implement the correct sorting function at the correct time - I think that should be something you can manage yourself. Otherwhise I'm happy to help :)

read more about array sorting (javascript docs: https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Array/sort)

Tim Gerhard
  • 3,477
  • 2
  • 19
  • 40
0
onSortChange = index => {
  const sortMap = {
    default: 'up',
    up: 'down',
    down: 'default',
  };

  let { currentSort, currentIndex } = this.state;

  let nextSort = currentIndex === index ? sortMap[currentSort] : 'default';

  let newRows = [...this.props.rows];

  switch (nextSort) {
    case 'up':
      newRows.sort((a, b) => (a.cells[index] <= b.cells[index] ? -1 : 1));
      break;
    case 'down':
      newRows.sort((a, b) => (b.cells[index] <= a.cells[index] ? -1 : 1));
      break;
  }

  this.setState({ rows: newRows, currentSort: nextSort, currentIndex: index });
};

First, you should not call the sort method directly on props.rows. Because it will directly modify props.rows instead of returning a sorted new array. So we first get a copy of the original data through [...this.props.rows] and then sort it.

Secondly, the usege of sort in your code is wrong. The first parameter compareFn of sort only compares the size relationship between two elements without worrying about anything else. And the order should be determined by the last sort order recorded in the state. In any case, the two elements passed to compareFn by sort are not given in order. It will different depending on the algorithm, but in simple terms, you can think that the task of compareFn is only to compare the size of two random elements in the array.

Finaly, in order to not be affected by the last sort result when click on a defferent column. We added the index of the last sorted column to the state. When the indexes of the last two sorted columns are not the same, use the up order to sort directly.

  • I used your method but I get an error for the line in state where rows is defined: `this.state = { rows: this.props.rows, ... }` and also for the last line in `onSortChange` method where is `this.setState({ rows: newRows, ... })`. The error message: Unused state field: 'rows' react/no-unused-state. Any idea why is this appearing? – Leo Messi Aug 07 '20 at 07:48