Home > Software design >  The table content is disappeared after sorting
The table content is disappeared after sorting

Time:05-21

I'm new to ReactJS. I have created a table with 2 columns. Here is my initial code:

import React, { useState, useEffect } from 'react'
import { getUsers } from '../../services/userService'
import { useNavigate } from 'react-router-dom'

const Table = () => {

    const navigate = useNavigate()

    const [users, setUsers] = useState([]);
    const [currentUsers, setCurrentUsers] = useState([]);
    const [search, setSearch] = useState('');

    useEffect(async () => {
        try {
            const response = await getUsers(search);
            setUsers(response.data.users);
        } catch (error) { }
    }, [search]);
    
    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'>name</th>
                        <th className='p-2'>phone</th>
                    </tr>
                </thead>
                <tbody>
                    {currentUsers.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.name}</td>
                            <td className='text-text text-sm p-2'>{item.phone}</td>
                        </tr>
                    )}
                </tbody>
            </table>
        </div>
    )
}

export default Table

I'm trying to sort this table by clicking on each column header. So I changed my code and here is the final code:

import React, { useState, useEffect } from 'react'
import { getUsers } from '../../services/userService'
import { useNavigate } from 'react-router-dom'

const Table = () => {

    const navigate = useNavigate()

    const [users, setUsers] = useState([]);
    const [currentUsers, setCurrentUsers] = useState([]);
    const [search, setSearch] = useState('');
    const [sorting, setSorting] = useState({ key: "name", ascending: true });

    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]);

    /**************************************** */
    useEffect(() => {
        const currentUsersCopy = [...currentUsers];
    
        const sortedCurrentUsers = currentUsersCopy.sort((a, b) => {
          return a[sorting.key].localeCompare(b[sorting.key]);
        });
    
        setCurrentUsers(
          sorting.ascending ? sortedCurrentUsers : sortedCurrentUsers.reverse()
        );
    }, [currentUsers, sorting]);

    function applySorting(key, ascending) {
        setSorting({ key: key, ascending: ascending });
    }

    /************************************** */

const changePage = (i) => {
    setCurrentPage(i)
    const startItem = ((i - 1) * pageItemCount)   1
    setCurrentUsers(users.slice(startItem - 1, (pageItemCount * i)))
}

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

    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={() => applySorting('name', !sorting.ascending)}>name</th>
                        <th className='p-2' onClick={() => applySorting('name', !sorting.ascending)}>phone</th>
                    </tr>
                </thead>
                <tbody>
                    {currentUsers.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.name}</td>
                            <td className='text-text text-sm p-2'>{item.phone}</td>
                        </tr>
                    )}
                </tbody>
            </table>
        <Pagination className="mt-2 pb-20" dir='ltr' page={currentPage} count={pageCount} onChange={handleChange} variant="outlined" shape="rounded" />

        </div>
    )
}    
export default Table

The problem is that when I changed my code, the content of my table disappeared. Although the entered users still exists and are added to my database, they are not displayed in the database. What's wrong?

CodePudding user response:

I would not recommend that you constantly change the original array, this is unreliable, try rewriting useEffect. I'm guessing that since you're constantly calling setCurrentUsers this changes the reference to currentUsers and triggers useEffect again as the dependency reference has changed and your code is cycling. try something like this to reach your goals:

const sortedUsers = useMemo(() => {
    const currentUsersCopy = [...currentUsers];
    const sortedCurrentUsers = currentUsersCopy.sort((a, b) => {
       return a[sorting.key].localeCompare(b[sorting.key]);
     });
     return sorting.ascending ? sortedCurrentUsers : sortedCurrentUsers.reverse();
}, [sorting, currentUsers])

This way you only sort users if new data arrives or sorting is updated.

Including I don’t see the place where you update the currentUsers, which you use to render the list, you save data from the api only in users

You can also leave the logic with useEffect, but remember that if you change the state based on the previous value, then do not use it directly on, but instead use the callback inside setState:

setState((prevState) => /* sorting logic */)

https://reactjs.org/docs/hooks-reference.html#functional-updates

CodePudding user response:

@Валера Битковский is right, there are a few things:

  1. you should sort the users and not the current users to avoid an infinite loop
  2. there is no need to have a currentUsers state - it is derived from the users sort state.
  3. useEffect runs after the render, there is no need for it if you are reacting to your own state change, useMemo runs during the render only if the dependencies changed.

I'll just rewrite your code to make it simpler

import React, { useState, useEffect } from 'react'
import { getUsers } from '../../services/userService'
import { useNavigate } from 'react-router-dom'

const Table = () => {

const navigate = useNavigate()

const [users, setUsers] = useState([]);
const [search, setSearch] = useState('');
const [sorting, setSorting] = useState({ key: "name", ascending: true });

useEffect(async () => {
    try {
        const response = await getUsers(search);
        setUsers(response.data.users);
    } catch (error) { }
}, [search]);

/**************************************** */
const sortedUsers = useMemo(() => {
    const sortedCurrentUsers = [...users].sort((a, b) => {
      return a[sorting.key].localeCompare(b[sorting.key]);
    });

    return sorting.ascending ? sortedCurrentUsers : sortedCurrentUsers.reverse();
}, [users, sorting]);



function applySorting(key, ascending) {
    setSorting({ key: key, ascending: ascending });
}

/************************************** */

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={() => applySorting('name', !sorting.ascending)}>name</th>
                    <th className='p-2' onClick={() => applySorting('name', !sorting.ascending)}>phone</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.name}</td>
                        <td className='text-text text-sm p-2'>{item.phone}</td>
                    </tr>
                )}
            </tbody>
        </table>
    </div>
)
}    
export default Table
  • Related