Reputation: 2391
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
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
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 />;
}
Upvotes: 1