myTest532 myTest532
myTest532 myTest532

Reputation: 2391

React Custom Table Sort

I'm trying to implement a custom table sort in React to sort when click on the header.

After I click on a header, the component is not render with the new posts array. Why? I'm trying to make it work without using a framework or library.

Thanks

*** I updated the code with the an option to first click sort asc and second click change too desc. The sorting is all messed. Is there something wrong with my sort function or my map where I keep the last sorted option?

import React, {useEffect, useState} from 'react';
import axios from 'axios';

const url = 'https://jsonplaceholder.typicode.com/posts';

const Sort = () => {
    const [posts, setPosts] = useState([]);
    
    const keySort = new Map();
    keySort.set("id", 0);
    keySort.set("title", 0);
    keySort.set("body", 0);

    useEffect(() => {
        async function fetchData() {
            const data = await axios.get(url);
            if(data.status !== 200) 
                console.error(data.statusText);
            else {
                setPosts(data.data);
            }
        }
        fetchData();
    }, []);

    const handleHeaderClick = (event) => {
        const id = event.target.id;
        setPosts((currPosts) => [...currPosts].sort((a,b) => {
            const keySortState = keySort.get(id) || 0;
            if(keySortState >= 0) {
                keySort.set(id, -1)
                if(a[id] > b[id])   return 1
                if(a[id] < b[id])   return -1
                return 0;
            }
            keySort.set(id, 0)
            if(a[id] > b[id])   return -1
            if(a[id] < b[id])   return 1
            return 0;   
        }));
    }

    return (
        <>
            <p>Sort Page</p>
            <table style={{border: '1px solid'}}>
                <thead>
                    <tr>
                        <th id="id" onClick={handleHeaderClick}>id</th>
                        <th id="title" onClick={handleHeaderClick}>Title</th>
                        <th id="body" onClick={handleHeaderClick}>Body</th>
                    </tr>
                </thead>
                <tbody>
                    {posts.map((obj, index) => {
                        return (
                            <tr key={index}>
                                <td>{obj.id}</td>
                                <td>{obj.title}</td>
                                <td>{obj.body}</td>
                            </tr>
                        )
                    })}
                </tbody>
            </table>
        </>
    );
}

export default Sort;

Upvotes: 0

Views: 503

Answers (2)

amitlisha
amitlisha

Reputation: 140

When mutating state in React, you should never mutate the original state object. Your mistake was in this line const data = posts;, you passed data the reference of posts and thus modified the original object. Instead, you should have send setState a callback function, which accepts the current posts array and then spread it - ... and modify it the way you wanted. You can read in the React official docs why it's better to pass setState a funciton.

How it should look :

  const handleHeaderClick = (event) => {
    const id = event.target.id;
    setPosts((currPosts) => [...currPosts].sort((a,b) => b[id] - a[id]));
}

Upvotes: 1

Majid M.
Majid M.

Reputation: 4974

Change const data = posts to const data = [...posts] Because you're mutating the original state.

Also you need to change the sort function to:

data.sort((a, b) => {
  if(a[id] < b[id]) return -1;
  if(a[id] > b[id]) return 1;
});

Here's the code:

import "./styles.css";
import React, { useEffect, useState } from "react";
import axios from "axios";

const url = "https://jsonplaceholder.typicode.com/posts";

const Sort = () => {
  const [posts, setPosts] = useState([]);

  useEffect(() => {
    async function fetchData() {
      const data = await axios.get(url);
      if (data.status !== 200) console.error(data.statusText);
      else {
        setPosts(data.data);
      }
    }
    fetchData();
  }, []);

  const handleHeaderClick = (event) => {
    const id = event.target.id;
    const data = [...posts];
    data.sort((a, b) => {
      if(a[id] < b[id]) return -1;
      if(a[id] > b[id]) return 1;
    });
    setPosts(data);
  };

  return (
    <>
      <p>Sort Page</p>
      <table style={{ border: "1px solid" }}>
        <thead>
          <tr>
            <th
              id="id"
              style={{ cursor: "pointer" }}
              onClick={handleHeaderClick}
            >
              id
            </th>
            <th
              id="title"
              style={{ cursor: "pointer" }}
              onClick={handleHeaderClick}
            >
              Title
            </th>
            <th
              id="body"
              style={{ cursor: "pointer" }}
              onClick={handleHeaderClick}
            >
              Body
            </th>
          </tr>
        </thead>
        <tbody>
          {posts.map((obj, index) => {
            return (
              <tr key={index}>
                <td>{obj.id}</td>
                <td>{obj.title}</td>
                <td>{obj.body}</td>
              </tr>
            );
          })}
        </tbody>
      </table>
    </>
  );
};

export default function App() {
  return <Sort />;
}

Edit hardcore-gagarin-5gf88

Upvotes: 1

Related Questions