I am sorting a table and when I click, the order goes to ascending but not descending. I have a boolean value that should toggle true or false but it keeps logging out the initial value. Can someone help me understand why the if statement isn't working? Also when I sort how do I return to the original array?
let sortDirection = false;
const sortTable = (key) => {
sortDirection = !sortDirection
const clonedOptions = [...listOfOptions];
clonedOptions.sort((a, b) => {
return sortDirection ? a[key] - b[key] : b[key] - a[key];
})
setListOfOptions(clonedOptions);
}
<div className="outputs" >
<table>
<thead>
<tr>
<th></th>
<th onClick={() => sortTable('clock')}>Date </th>
<th onClick={() => sortTable('name')}>Stock Name</th>
<th onClick={() => sortTable('price')}>Price Of Option</th>
<th onClick={() => sortTable('amountOfOptions')}>Number Of Options</th>
<th onClick={() => sortTable('totalAmountSpent')}>Total Amount Spent</th>
<th onClick={() => sortTable('optionPriceSoldAt')}>Option Sold At</th>
<th onClick={() => sortTable('amountOfOptionsSold')}>Amount Of Options Sold</th>
<th onClick={() => sortTable('totalProfit')}>Proft</th>
</tr>
</thead>
{listOfOptions.map((option) => {
return (
<tbody key={uuidv1()}>
<tr>
<td title="delete" onClick={() => deleteOption(option.id)}><span className="delete">x</span></td>
<td>{option.clock}</td>
<td>{option.name.toUpperCase()}</td>
<td>${option.price}</td>
<td>{option.amountOfOptions}</td>
<td>${option.totalAmountSpent.toFixed(2)}</td>
<td>${option.optionPriceSoldAt}</td>
<td>{option.amountOfOptionsSold}</td>
<td style={{ color: option.totalProfit >= 0 ? 'green' : 'red' }}>${option.totalProfit.toFixed(2)}</td>
</tr>
</tbody>
)
})}
</table>
</div>
</div>
You need to put the boolean into state, otherwise, on each render, it'll start out as false:
const [sortDirection, setSortDirection] = useState(false);
const sortTable = (key) => {
setSortDirection(!sortDirection);
const clonedOptions = [...listOfOptions];
clonedOptions.sort((a, b) => {
return sortDirection ? b[key] - a[key] : a[key] - b[key];
})
setListOfOptions(clonedOptions);
}
Related
My main component
Here I'm fetching data from backend and receiving it well. Here how it looks like.
And now I want to sort them by their properties like step 1, step 2. I'm using React query to fetch data but I'm not sure how to sort it. Also, I already have sorting functions. But, I don't know how to change data based on the sorting atribute.
.
import React, { useEffect, useState } from "react";
import useFetchTable from "../../../../api/table/useFetchTable";
const TableList = () => {
const { data: response, status, isLoading } = useFetchTable();
// const [sortField, setSortField] = useState("");
// const [order, setOrder] = useState("asc");
// const handleSortingChange = (accessor) => {
// const sortOrder =
// accessor === sortField && order === "desc" ? "asc" : "desc";
// setSortField(accessor);
// setOrder(sortOrder);
// handleSorting(accessor, sortOrder);
// };
// const handleSorting = (sortField, sortOrder) => {
// if (sortField) {
// const sorted = [...data].sort((a, b) => {
// if (a[sortField] === null) return 1;
// if (b[sortField] === null) return -1;
// if (a[sortField] === null && b[sortField] === null) return 0;
// return (
// a[sortField].toString().localeCompare(b[sortField].toString(), "en", {
// numeric: true,
// }) * (sortOrder === "asc" ? 1 : -1)
// );
// });
// setData(sorted);
// }
// };
if (status === "error") {
return "Error";
}
if (isLoading) {
return "Loading...";
}
console.log(response);
const Print = ({ children }) => {
return (
<span className="text-xs bg-blue-100 rounded-full px-2 py-0.5 ml-2">
{children}%
</span>
);
};
return (
<div>
<table>
<thead className="border-b-2">
<tr>
<th className="py-1">Product Name</th>
<th>Purchases</th>
<th>US</th>
<th>Ch Step 1</th>
<th>Ch Step 2</th>
<th>CVR</th>
<th> 1</th>
<th>Upsell 2</th>
<th>Upsell 3</th>
</tr>
</thead>
<tbody>
{response.data?.map((row, idx) => (
<tr key={idx}>
<td>{row.name}</td>
<td>
{row.purchases[0]} <Print>{row.purchases[1]}</Print>
</td>
<td>
{row.unique_sessions} <Print>100</Print>
</td>
<td>
{row.checkout_step_1[0]} <Print>{row.checkout_step_1[1]}</Print>
</td>
<td>
{row.checkout_step_2[0]} <Print>{row.checkout_step_2[1]}</Print>
</td>
<td>
<Print>{`${row["cvr_%"]}`}</Print>
</td>
<td>
{row.upsell_1_takes[0]} <Print>{row.upsell_1_takes[1]}</Print>
</td>
<td>
{row.upsell_2_takes[0]} <Print>{row.upsell_2_takes[1]}</Print>
</td>
<td>
{row.upsell_3_takes[0]} <Print>{row.upsell_3_takes[1]}</Print>
</td>
</tr>
))}
</tbody>
</table>
TableList
{/* {data?.map((el) => {
el.title;
})} */}
</div>
);
};
export default TableList;
So for sorting based on your column header you can create a function to handle that onClick of the particular header. Like in the below code I have used the firstName column for sorting. On clicking the first name header it will trigger the function sortByFirstName and added the sort functionality in it and updated the state of the setTableData . Hope this helps.
import React, { useEffect, useState } from 'react'
import { useQuery } from 'react-query'
import './style.css'
function Example () {
const [sorted, setSorted] = useState({ sorted: "fname", reversed: false });
const [tableData, setTableData] = useState([])
const { data } = useQuery({
queryKey: ['repoData'],
queryFn: () =>
fetch('https://dummyjson.com/users?limit=10').then(
(res) => res.json(),
),
})
useEffect(() => {
if (data) {
setTableData(data?.users)
}
}, [data])
const sortByFirstName = () => {
setSorted({ sorted: "fname", reversed: !sorted.reversed })
const tableDataCopy = [...tableData];
tableDataCopy.sort((a, b) => {
let fnameA = a.firstName.toLowerCase();
let fnameB = b.firstName.toLowerCase();
if (sorted.reversed) {
return fnameB.localeCompare(fnameA)
}
return fnameA.localeCompare(fnameB)
})
setTableData(tableDataCopy)
}
return (
<div className='h-full w-full'>
<table className='data' cellspacing="0" cellpadding="0">
<thead>
<tr>
<th onClick={ sortByFirstName }>First Name</th>
<th >Last Name</th>
<th >Gender</th>
<th >Email</th>
<th >Bloodgroup</th>
<th >Age</th>
<th > Weight</th>
<th >Maiden Name</th>
<th >Phone</th>
</tr>
</thead>
<tbody>
{ tableData?.map((row, idx) => (
<tr key={ idx }>
<td>{ row.firstName }</td>
<td>
{ row.lastName }
</td>
<td>
{ row.gender }
</td>
<td>
{ row.email }
</td>
<td>
{ row.bloodGroup }
</td>
<td>
{ row.age }
</td>
<td>
{ row.weight }
</td>
<td>
{ row.maidenName }
</td>
<td>
{ row.phone }
</td>
</tr>
)) }
</tbody>
</table>
</div>
)
}
export default Example
I am creating a table dynamically in React with the API response as it is as it receives.
data = {"name":"tom", "age":23, "group":null, "phone":xxx}
It is working fine with that, but the group key sometimes contains another object, so I want to create a sub-table whenever there is another object inside a key.
For example:
data = {"name":"tom", "age":23, "group":{"id":123, "joined_at":12-jan-2022}, "phone":xxx}
My current code is:
<table>
{Object.keys(data).map(index=> (
<tr>
<td>{index}</td>
<td>{data[index]?.toString()}</td>
</tr>
))}
</table>
The code you have shared would change as follows:
<table>
{Object.keys(data).map((item, index) => {
if (group) {
return (
<>
<tr>
<td>{index}</td>
<td>{item.toString()}</td>
</tr>
<tr>
<table>
<tr>
<td>{group.id}</td>
<td>{group.joined_at}</td>
</tr>
</table>
</tr>
</>
);
}
return (
<tr>
<td>{index}</td>
<td>{item.toString()}</td>
</tr>
);
})}
</table>;
You can fix the formatting as per your needs but this is essentially how you would create a sub-table based on the value of group
A slightly improved version would be :
const normalTableRow = (index, item) => {
return (
<tr>
<td>{index}</td>
<td>{item.toString()}</td>
</tr>
);
};
<table>
{Object.keys(data).map((item, index) => {
if (group) {
return (
<>
{normalTableRow(index, item)}
<tr>
<table>
<tr>
<td>{group.id}</td>
<td>{group.joined_at}</td>
</tr>
</table>
</tr>
</>
);
}
return normalTableRow(index, item);
})}
</table>;
EDIT: updating solution based on comment
const normalTableRow = (index, item) => {
return (
<tr>
<td>{index}</td>
<td>{item.toString()}</td>
</tr>
);
};
const isObject = (value) => {
return typeof value === 'object' && !Array.isArray(value) && value !== null;
};
<table>
{Object.keys(data).map((item, index) => {
let subTables = Object.keys(item).map((key) => {
if (isObject(item[key])) {
return (
<tr>
<table>
<tr>
{Object.keys(item[key]).map((subKey) => {
return <td>{item[key][subKey]}</td>;
})}
</tr>
</table>
</tr>
);
}
});
return (
<>
{normalTableRow(index, item)}
{[...subTables]}
</>
);
})}
</table>;
So I've got this table in React. It works fine with all the columns except the one with the country name. I checked API so className definitely matches with it and I am a bit clueless how to fix it.
const Table = () => {
const[country, setCountry] = useState([]);
const[toggle, setToggle] = useState(true);
const sortColumnNumber = (sort, columnName, data) => {
data = data.sort((a, b) => {
return sort ? b[columnName] - a[columnName] : a[columnName] - b[columnName];
});
}
useEffect(() => {
const loadData = async() => {
await fetch('https://api.covid19api.com/summary')
.then(response => response.json())
.then(data => {
const stats = data.Countries;
sortColumnNumber(toggle, 'TotalConfirmed', stats)
setCountry(stats);
})
}
loadData();
}, []);
return(
<React.Fragment>
<table className="table table-bordered table-stripped">
<thead >
<tr onClick={(e) =>{
setToggle(!toggle);
sortColumnNumber(toggle, e.target.className, country);
}} style={{cursor: "pointer"}} className="thead-dark">
<th className="Country" scope="col">Country</th>
<th className="TotalConfirmed" scope="col">Total Cases</th>
<th className="NewConfirmed" scope="col">New Cases</th>
<th className="NewDeaths" scope="col">New Deaths</th>
<th className="TotalDeaths" scope="col">Total Deaths </th>
<th className="TotalRecovered" scope="col">Total Recovered </th>
</tr>
</thead>
<tbody>
<CountryStats country={country} />
</tbody>
</table>
</React.Fragment>
)
}
Try something like that:
const sortColumnNumber = (sort, columnName, data) => {
data = data.sort((a, b) => {
let [first, second] = sort ? [a, b] : [b, a];
if (first[columnName] < second[columnName]) return -1;
if (first[columnName] > second[columnName]) return 1;
return 0;
});
};
It's important to return -1 or 0 or 1 to avoid problem with data types.
See more about sort method in this link: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort
It doesn't work with country name because it is a String type and your callback method logic will not work for sorting Strings.
Subtracting two String values results in NaN.
Refer to this SO answer for sorting Strings.
I want to run a filter method on the renderTable method that I've defined below. Is it possible to do:
renderTable().filter(x => x.name)?
Right now, I have a table with rows of each category and their results from a url that provided some json data.
I would like to make a feature that allows users to adjust the setting to return their desired results.
CODE
const renderTable = () => {
return players.map(player => {
return (
<tr>
<td>{player.name}</td>
<td>{player.age}</td>
<td>{player.gender}</td>
<td>{player.state}</td>
<td>{player.status}</td>
</tr>
)
})
}
return (
<div className = "App">
<h1>Players</h1>
<table id = "players">
<thead>
<tr>
<th>Name</th>
<th>Age</th>
<th>Gender</th>
<th>State</th>
<th>Status</th>
</tr>
</thead>
<tbody>{renderTable()}</tbody>
</table>
</div>
);
You can apply the filter before the map. Here, give this a try:
const renderTable = () => {
return players
.filter(player => player.state === "NSW")
.map(player => {
return (
<tr>
<td>{player.name}</td>
<td>{player.age}</td>
<td>{player.gender}</td>
<td>{player.state}</td>
<td>{player.status}</td>
</tr>
);
});
};
Here's a Working Sample CodeSandbox for your ref.
Yes you can. Just filter before map players.filters(...).map. But this is better
const renderTable = () => {
const rows = []
players.forEach((player, index) => {
if(player.name) {
rows.push(
<tr key={index}>
<td>{player.name}</td>
<td>{player.age}</td>
<td>{player.gender}</td>
<td>{player.state}</td>
<td>{player.status}</td>
</tr>
)
}
})
return rows;
}
I'm new to React Hooks. I'm trying to sort table inside React Hook, but after click my content doesn't update. Why is that?
This is my hook:
const Main = ({ dataProps }) => {
const [data, setData] = useState(dataProps);
const sortById = (field) => {
let sortedData = data.sort((a, b) => {
if(a[field] < b[field]) { return -1; }
if(a[field] > b[field]) { return 1; }
return 0;
});
setData(sortedData);
};
return (
<table>
<thead>
<tr>
<th>iD <div className="arrows"><div onClick={() => sortById("id")} className="arrow-up"/></th>
<th>First name <div className="arrows"><div onClick={() => sortById("firstName")} className="arrow-up"/></div></th>
</tr>
</thead>
<tbody>
{data.map((user) => {
return <tr key={user.id}>
<td className="number">{user.id}</td>
<td className="firstname">{user.firstName}</td>
</tr>
})}
</tbody>
</table>
);
};
problem is sort return same array which won't cause re-render. if you slice before sorting it will fix issue.
const sortById = (field) => {
let sortedData = data.slice().sort((a, b) => {
if(a[field] < b[field]) { return -1; }
if(a[field] > b[field]) { return 1; }
return 0;
});
setData(sortedData);
};
BTW more efficient way of doing is mentioned by #jonas-wilms
sortById sorts the data array. But it does not trigger the component to rerender. The field here is obviously a state:
const [data, setData] = useState(dataProps);
const [field, setField] = useState(null);
const sorted = useMemo(() => {
if(!field) return data;
return data.slice().sort((a, b) => a[field].localeCompare(b[field]));
}, [ data, field]);
return <div>
<a onClick={() => setField("firstName")}> Sort by Name</a>
{sorted.map(/*...*/)}
</div>;