'sort table based on a certain column

I'm new to both react and Tailwind CSS. I've created a table. Table columns are related (each name in the 1st column has a related mobile number in the 2nd column). I want to add an option on each column of this table, so that when I click on the header of a column, the table rows become sorted (alphabetically or numerically) according to that column. Here is the code:

import React, { useState, useEffect } from 'react'
import { getUsers } from '../../services/userService'

const Table = () => {

    const [users, setUsers] = useState([]);
    const [currentUsers, setCurrentUsers] = useState([]);
    const [search, setSearch] = useState('');
    const [isSorted, setIsSorted] = useState(false);
    const [sortedUsers, setSortedUsers] = useState([]);


    useEffect(async () => {
    try {
        const response = await getUsers(search);
        setUsers(response.data.users);
        setPageCount(Math.ceil(response.data.users.length / pageItemCount))
        setCurrentUsers(response.data.users.slice(0, pageItemCount))
    } catch (error) { }
}, [search]);

    const handleChange = (event, value) => {
        changePage(value);
    }

const sortFn = (userA, userB) => {
  // sort logic here, it can be whatever is needed
  // sorting alphabetically by `first_name` in this case
  return userA.first_name.localeCompare(userB.first_name)
}

const toggleSort = () => {
  setIsSorted(!isSorted)
}

// when `currentUsers` changes we want to reset our table
// in order to keep it in sync with actual values
// we're also sorting if we were already sorting
useEffect(() => {
  if (isSorted) {
    setSortedUsers(currentUsers.slice().sort(sortFn))
  } else {
    setSortedUsers(currentUsers)
  }
}, [isSorted, currentUsers])

    return (
        <div dir='rtl' className='bg-background mt-10 px-5 rd1200:px-30 overflow-auto'>
           
            <table className='w-full border-separate rounded-md'>
                <thead>
                    <tr className='bg-text-secondary text-white shadow-sm text-center'>
                        <th className='p-2' onClick={(e) => toggleSort()}>name</th>
                        <th className='p-2' onClick={(e) => toggleSort()}>mobile</th>
                    </tr>
                </thead>
                <tbody>
                    {sortedUsers.map((item, index) =>
                        <tr key={item.id} className={index % 2 === 0 ? 'bg-white shadow-sm text-center' : 'bg-text bg-opacity-5 shadow-sm text-center'}>
                            <td className='text-text text-sm p-2'>{item.first_name}</td>
                            <td className='text-text text-sm p-2'>{item.mobile}</td> 
                        </tr>
                    )}
                </tbody>
            </table>
            
        </div>
    )
}

export default Table

The code is working fine. The only problem is that the table is only sorted based on the first name regardless of which column header I click on (So when I click on the mobile column header, the table is still sorted based on the first_name). How can I change it, so that the table content become sorted according to the clicked column header?



Solution 1:[1]

You should have some state which holds the key which the data is currently sorted by. Whenever a header is clicked update that state to whatever column was clicked.

On every re-render you can then use the key to access the values to sort by.

Please note: This sort() function will just compare strings and numbers and will only compare if the datatype matches.

const users = [
  { fname: "Thomas", lname: "Fox", age: 51 },
  { fname: "John", lname: "Mayor", age: 18 },
  { fname: "Ronny", lname: "Bush", age: 32 },
  { fname: "Aaron", lname: "Schulz", age: 73 },
];
const cols = [
  { key: "fname", text: "First name" },
  { key: "lname", text: "Last name" },
  { key: "age", text: "Age" },
];

const Table = () => {
  const [data, setData] = React.useState(users);
  const [columns, setColumns] = React.useState(cols);
  const [sortedBy, setSortedBy] = React.useState(columns[0].key);

  console.log(`Sorting by column ${sortedBy}`);
  const sorted = data.sort((a, b) => {
    const aVal = a[sortedBy];
    const bVal = b[sortedBy];
    if (typeof aVal === "number" && typeof bVal === "number") return aVal - bVal;
    else if (typeof aVal === "string" && typeof bVal === "string") return aVal.localeCompare(bVal);
    return 1;
  });

  return (
    <table>
      <thead>
        <tr className="">
          {columns.map((col) => (
            <th onClick={() => setSortedBy(col.key)}>{col.text}</th>
          ))}
        </tr>
      </thead>
      <tbody>
        {sorted.map((item) => (
          <tr>
            <td>{item.fname}</td>
            <td>{item.lname}</td>
            <td>{item.age}</td>
          </tr>
        ))}
      </tbody>
    </table>
  );
};

ReactDOM.render(<Table />, document.getElementById("root"));
th { 
  border: solid thin; 
  padding: 0.5rem;
}

table {
  border-collapse: collapse;
}
<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="root"></div>

Instead of just the key you could additionally store whether to store in ascending or descending order for example when we click on a column again we toggle the descending order. This is just a basic example which should give you some idea on how to implement such functionality.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1