Using the function includes i want to use conditional rendering to check if different id's exist in the arrray. no matter what value i put in the includes function it always returns false even if i put Null. When i tried to test the length function i get the correct length.
When i console log it i get all the values.
Decleration of useState:
const [freeSlotsList, setFreeSlotsList] = useState([]);
console.log(freeSlotsList)
useEffect(() => {
Axios.post("http://localhost:3001/api/get/week1/ex").then((response) => {
setFreeSlotsList(response.data);
});
}, []);
It's always false:
const renderTableData = () => {
let id = 1;
const activeButton = () => {};
return (
<tr>
{days.map((val) => (
<td>
{timeSlot.map((n, i) => {
if (freeSlotsList.includes(id) == false) {
return <h1>Testing</h1>;
}
return (
<button id={id++} className={activeButton}>
{n} {id}
</button>
);
})}
</td>
))}
</tr>
);
};
I have a react-table with a primary header containing multiple secondary headers underneath it. When the primary header is clicked I want all but one secondary header with name 'deposit' to be hidden, or shown (toggle). See screenshot.
I have a solution using column.toggleHeader(column.isVisible). I both wanted to demonstrate this as there wasn't a lot of material out there for how to do this; and I'm wondering if there are neater solutions using .getToggleHiddenProps. To be honest I don't understand what is going on with IndeterminateCheckbox eg. https://github.com/TanStack/table/discussions/1989 and how that would be used with specific column header values.
My answer below.
Just some background - this is a Web3 app that is getting information about Crypto wallets - hence why the code looks for '0x' and reduces the column header value to const headerShortened = header.substr(0,5) + '...' + header.substr(header.length - 4,header.length) (So 0x123abcFED1239876 becomes ox123...9876)
I've just finished this initial version so don't AT me for any rough edges. Though happy to receive constructive feedback.
interface DataTableProps {
data: any
columns: Column<DripRowFormat>[]
}
interface AHeaderProps {
column: ColumnInstance<DripRowFormat>
allColumns: Array<ColumnInstance<DripRowFormat>>
}
export function DataTable({data, columns}: DataTableProps) {
const [hiddenColumns, setHiddenColumns] = useState<string[]>([])
const initialState = {hiddenColumns}
const {
getTableProps,
getTableBodyProps,
headerGroups,
rows,
prepareRow,
allColumns,
getToggleHideAllColumnsProps,
state
} = useTable({columns, data, initialState});
// console.log(`table input: ${JSON.stringify(data, null, 2)}`)
// console.log(`table rows: ${Util.inspect(rows)}`)
// console.log(`getTableProps: ${JSON.stringify(getTableProps())}`)
// console.log(`allColumns: ${Util.inspect(allColumns)}`)
const RenderHeader = ({column, allColumns}: AHeaderProps) => {
const colHeaderClick = (e: React.MouseEvent<HTMLButtonElement>) => {
e.preventDefault()
// Now call column.toggleHidden() for all cols but 'deposit' - so when Hidden only Deposit is shown
const childColumnsNotDeposit = allColumns.filter(c =>
column.Header && c.Header && c.parent
&& c.Header.toString().toLowerCase() !== 'deposit'
&& c?.parent?.Header?.toString() === column.Header)
childColumnsNotDeposit.forEach(c => {
c.toggleHidden(c.isVisible)
})
}
const drawHeaderWRTVisibleColumns = (column: ColumnInstance<DripRowFormat>): ReactNode => {
if (! column) {
return (
<span>NOT COL</span>
)
}
const childCols = column.columns?.filter(c => c.isVisible)
// #ts-ignore
if (childCols && childCols.length < column?.columns?.length) {
// #ts-ignore
const header = column.Header.toString()
const headerShortened = header.substr(0,5) +
'...' + header.substr(header.length - 4,header.length)
return (
<span>{headerShortened}</span>
)
} else {
return column.render('Header')
}
}
// #ts-ignore
if (column.placeholderOf?.Header.startsWith('0x')) {
return (
<button onClick={colHeaderClick}>{drawHeaderWRTVisibleColumns(column)}</button>
)
} else if (column.Header?.toString().startsWith('0x')) {
return (
<button onClick={colHeaderClick}>{drawHeaderWRTVisibleColumns(column)}</button>
)
} else {
return (
<span>{column.render('Header')}</span>
)
}
}
return (
<div>
<table {...getTableProps()}>
<thead>
{headerGroups.map(headerGroup => (
<tr {...headerGroup.getHeaderGroupProps()}>
{headerGroup.headers.map(column => (
// #ts-ignore
<th {...column.getHeaderProps()}><RenderHeader column={column} allColumns={allColumns}/></th>
))}
</tr>
))}
</thead>
<tbody {...getTableBodyProps()}>
{rows.map((row, i) => {
prepareRow(row)
return (
<tr {...row.getRowProps()}>
{row.cells.map(cell => {
return <td {...cell.getCellProps()}>{cell.render('Cell')}</td>
})}
</tr>
)
})}
</tbody>
</table>
</div>
);
}
I have a table with 2 columns containing users info. I have divided the the table users in multiple pages, so that table of each page only displays 15 users. I have also implemented sorting on this table, so that when I click on each column header, the table is sorted according to this 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 [valueHeader, setValueHeader] = useState({title: "",body: ""}); //Value header state
const [sortedUsers, setSortedUsers] = useState([]);
const pageItemCount = 15
const [pageCount, setPageCount] = useState(0)
const [currentPage, setCurrentPage] = useState(1)
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 sortFn = (userA, userB) => {
// sort logic here, it can be whatever is needed
// sorting alphabetically by `first_name` in this case
return userA[valueHeader.body].localeCompare(userB[valueHeader.body]) //<== Use value of column header
}
useEffect(() => {
if (isSorted) {
setSortedUsers(currentUsers.slice().sort(sortFn))
} else {
setSortedUsers(currentUsers)
}
}, [isSorted, currentUsers, valueHeader]) //<== add valueHeader to dependency
const toggleSort = (target) => {
setIsSorted(!isSorted)
setValueHeader({
title: target,
body: target == "name" ? "first_name" : "mobile_number"
}) //<=== set state of value header
}
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={()=>toggleSort("name")}>name</th>
<th className='p-2' onClick={()=>toggleSort("mobile")}>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_number}</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 only problem is that, since I display only 15 users of the table in each page, when I click on the column header, only the users of that page is sorted, but I want to apply sorting on all users of the table (the users of all pages). Is it possible?
Edited code according to suggested answer:
import React, { useState, useEffect } from 'react'
import { getUsers } from '../../services/userService'
const Table = () => {
const [users, setUsers] = useState([]);
const [sortDirection, setSortDirection] = useState("asc");
const [search, setSearch] = useState('');
const pageItemCount = 15
const [pageCount, setPageCount] = useState(2);
const [currentPage, setCurrentPage] = useState(1);
useEffect(async () => {
try {
const response = await getUsers(search);
setUsers(response.data.users);
setPageCount(Math.ceil(response.data.users.length / pageItemCount));
} catch (error) { }
}, [search]);
useEffect(() => {
toggleSort("name");
}, [])
const startItem = (currentPage - 1) * pageItemCount + 1;
const pagedUsers = users.slice(startItem - 1, pageItemCount * currentPage);
const sortFn = (fieldToSort, direction) => (userA, userB) => {
if (direction === "asc")
return userA[fieldToSort].localeCompare(userB[fieldToSort]);
else return userB[fieldToSort].localeCompare(userA[fieldToSort]);
};
const toggleSort = (target) => {
const direction = sortDirection === "asc" ? "desc" : "asc";
const fieldToSort = target === "name" ? "first_name" : "mobile_number";
setSortDirection(direction);
setUsers(users.slice().sort(sortFn(fieldToSort, direction)));
};
const handleChange = (event, value) => {
setCurrentPage(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={()=>toggleSort("name")}>name</th>
<th className='p-2' onClick={()=>toggleSort("mobile")}>mobile</th>
</tr>
</thead>
<tbody>
{pagedUsers?.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_number}</td>
</tr>
)}
</tbody>
</table>
{users.length > 0 && (
<Pagination
className="mt-2 pb-20"
dir="ltr"
page={currentPage}
count={pageCount}
onChange={handleChange}
variant="outlined"
shape="rounded"
/>
)}
</div>
)
}
export default Table
You have 2 methods
the first is sorting the users array but this will change the users in this page
The second is wich i prefere is to call the sorting function in the changepage function
The error it's in this useEffect logic:
useEffect(() => {
if (isSorted) {
// When its sorted, you are setting to sort currentUsers,
// and it holds only the first 15 users, not the entire users array
setSortedUsers(currentUsers.slice().sort(sortFn))
} else {
setSortedUsers(currentUsers)
}
}, [isSorted, currentUsers, valueHeader])
The below code should work for you:
useEffect(() => {
if (isSorted) {
// get all users and sort it,
// returning an immutable array because the use of .slice()
const usersUpdated = users.slice().sort(sortFn).slice(0, pageItemCount);
// Updated the currentUsers and sortedUsers states
setSortedUsers(usersUpdated);
setCurrentUsers(usersUpdated);
} else {
// Updated the currentUsers and sortedUsers states with the first 15 users
setSortedUsers(users.slice(0, pageItemCount));
setCurrentUsers(users.slice(0, pageItemCount));
}
// instead call the useEffect base on currentUsers, you change it to users
}, [isSorted, users, valueHeader]);
Having said that, just a point - You are using to many states for user:
. one for all users
. one for current users
. one for sorted users
You can handle this with only one state, i did a code sample to you check it.
The main cause of your issue is the line:
setSortedUsers(currentUsers.slice().sort(sortFn))
when I click on the column header, only the users of that page is sorted
It's not that strange that only the users on the current page are sorted, since that's exactly what the line of code above does.
Instead you want to sort first, then take the currentUsers from the sortedUsers.
I've written an answer, but did overhaul your code. The reason being is that you violate the single source of truth principal. Often resulting in bugs or strange behaviour due to a mismatch between the different sources of truth.
Examples of source of truth duplication is the fact that you store 3 lists of users users, currentUsers, and sortedUsers. The pageCount is essentially stored 2 times. One can be calculated by Math.ceil(users.length / pageItemCount), the other is stored in the pageCount state.
What happens if you change the users array length, but forget to adjust the pageCount?
Instead of storing the pageCount in a state, you can derive it from two available values. So there is no need for a state, instead use useMemo.
const pageCount = useMemo(() => (
Math.ceil(users.length, pageItemCount)
), [users.length, pageItemCount]);
Similarly you could derive sortedUsers and currentUsers from users. If you have access to the order in which the users should be sorted, the current page, and the maximum page size.
I've extracted some of the logic into separate functions to keep the component itself somewhat clean. Since you haven't given us a snippet or environment to work with I'm not sure if the code below works. But it should hopefully give you some inspiration/insight on how to handle things.
import React, { useState, useEffect, useMemo, useCallback } from 'react';
import { getUsers } from '../../services/userService';
// Returns a new object without the given keys.
function without(object, ...excludeKeys) {
excludeKeys = new Set(excludeKeys);
return Object.fromEntries(
Object.entries(object).filter(([key]) => !excludeKeys.has(key))
);
}
// Compares the given property value in both users.
// Returns -1, 0, or 1, based on ascending comparison.
function compareUserProp(userA, userB, prop) {
const valueA = userA[prop];
const valueB = userB[prop];
if (typeof valueA === "string" && typeof valueB === "string") {
return valueA.localeCompare(valueB);
}
if (valueA < valueB) return -1;
if (valueA > valueB) return 1;
return 0;
}
function isEven(integer) {
return integer % 2 === 0;
}
function getUserTRClass(index) {
if (isEven(index)) {
return 'bg-white shadow-sm text-center';
} else {
return 'bg-text bg-opacity-5 shadow-sm text-center';
}
}
function Table({ maxPageSize = 15 }) {
const [users , setUsers ] = useEffect([]); // [{ first_name: "John", last_name: "Doe", age: 42 }]
const [search , setSearch ] = useEffect("");
const [order , setOrder ] = useEffect({}); // { last_name: "asc", age: "desc" }
const [currentPage, setCurrentPage] = useEffect(1);
const pageCount = useMemo(() => (
Math.ceil(users.length / maxPageSize)
), [users.length, maxPageSize]);
const sortedUsers = useMemo(() => {
const modifier = { asc: 1, desc: -1 };
return Array.from(users).sort((userA, userB) => {
for (const [prop, direction] of Object.entries(order)) {
const diff = compareUserProp(userA, userB, prop);
if (diff) return diff * modifier[direction];
}
return 0;
});
}, [users, order]);
const usersOnPage = useMemo(() => {
const zeroBasedPage = currentPage - 1;
const beginIndex = zeroBasedPage * maxPageSize;
const endIndex = beginIndex + maxPageSize;
return sortedUsers.slice(beginIndex, endIndex);
}, [sortedUsers, currentPage, maxPageSize]);
// Do not pass an async function directly to `useEffect`. `useEffect` expects
// a cleanup function or `undefined` as the return value. Not a promise.
useEffect(() => {
(async function () {
const response = getUsers(search);
setUsers(response.data.users);
// setCurrentPage(1); // optional, reset page to 1 after a search
})();
}, [search]);
const toggleSort = useCallback((prop) => {
const inverse = { "desc": "asc", "asc": "desc" };
setOrder((order) => {
const direction = order[prop] || "desc";
return { [prop]: inverse[direction], ...without(order, prop) };
});
}, []);
const changePage = useCallback((_event, newPage) => {
setCurrentPage(newPage);
}, []);
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={() => toggleSort("first_name")}>name</th>
<th className='p-2' onClick={() => toggleSort("mobile_number")}>mobile</th>
</tr>
</thead>
<tbody>
{usersOnPage.map((user, index) => (
<tr key={user.id} className={getUserTRClass(index)}>
<td className='text-text text-sm p-2'>{user.first_name}</td>
<td className='text-text text-sm p-2'>{user.mobile_number}</td>
</tr>
))}
</tbody>
</table>
<Pagination className="mt-2 pb-20" dir='ltr' page={currentPage} count={pageCount} onChange={changePage} variant="outlined" shape="rounded" />
</div>
);
}
export default Table;
If you want to start the users of sorted simply set an initial value for order. For example:
const [order, setOrder] = useState({ last_name: "asc" });
The algorithm to solve this would be as follows
Maintain one state for your entire dataset.
state: allUsers
Capture the event of button click.
Applying sorting to the entire data based on event handler inputs you can decide the sort criterion.
allUsers.sort(criterionFunction);
// you may call an API for this step and bind result to allUsers if needed or do it on the client side.
Derive the slice of data set based on the limit and offset maintained in the local state.
usersInPage = allUsers.slice(offset,limit)
The derived data slice shall re-render itself on the pagination UI.
renderUsers(usersInPage)
I am trying to sort the HTML table, but getting the error. Is there anything I am doing wrongly?
I want to able to sort ascending and descending. I am using useMemo. To make it, I am sorting on search return called searchPosts and I have sortType and setSortType defined in my useState.
import React, {useState, useEffect, useMemo} from 'react'
import '../css/about.css';
import Pagination from '../components/Pagination'
function About() {
const [userData, setUserData] = useState([]);
const [loading , setLoading] = useState(false);
const [currentPage, setCurrentPage] = useState(1);
const [postsPerPage, setPostsPerPage] = useState(5);
const [search, setSearch] = useState("");
const [sortType, setSortType] = useState('asc');
async function getData()
{
let response = await fetch('https://api.github.com/users');
let data = await response.json();
return data;
}
//call getData function
getData()
.then(data => console.log(data)
);//
useEffect(() => {
setLoading(true)
getData()
.then(
data => {
setUserData(data) }
)
.catch(error => {
console.log(error);
})
}, [])
// Get current posts
const indexOfLastPost = currentPage * postsPerPage;
const indexOfFirstPost = indexOfLastPost - postsPerPage;
const currentPosts = userData.slice(indexOfFirstPost, indexOfLastPost);
// changd page
const paginate = (pageNumber) => setCurrentPage(pageNumber);
// Search Table
const handleFilterChange = e => {
setSearch(e.target.value)
}
// for search
function DataSearch(rows) {
const columns = rows[0] && Object.keys(rows[0]);
return rows.filter((row) =>
columns.some((column) => row[column].toString().toLowerCase().indexOf(search.toLowerCase()) > -1)
);
}
const searchPosts = DataSearch(currentPosts);
// **for Sorting**
const sorted = useMemo(() => {
if(!sortType) return searchPosts;
return searchPosts.slice().sort((a, b) => a[searchPosts].localeCompare(b[searchPosts]));
}, [searchPosts, sortType]);
return (
<div className="container">
<div>
<div>
<input value={search} onChange={handleFilterChange} placeholder={"Search"} />
</div>
<div> {loading }</div>
<table>
<thead>
<tr>
<th>id</th>
<th>avatar_url</th>
<th>events_url</th>
<th>followers_url</th>
<th>following_url</th>
<th>gists_url</th>
<th>gravatar_id</th>
<th>html_url</th>
<th>
login
<a onClick={() => setSortType("login")}> Sort by Login</a>
</th>
<th>node_id</th>
<th>organizations_url</th>
<th>received_events_url</th>
<th>repos_url</th>
<th>site_admin</th>
<th>starred_url</th>
<th>subscriptions_url</th>
<th>type</th>
<th>url</th>
</tr>
</thead>
<tbody>
{
sorted.map((item, index) => (
<tr key={index}>
<td>{item.id}</td>
<td>{item.avatar_url}</td>
<td>{item.events_url}</td>
<td>{item.followers_url}</td>
<td>{item.following_url}</td>
<td>{item.gists_url}</td>
<td>{item.gravatar_id}</td>
<td>{item.html_url}</td>
<td>{item.login}</td>
<td>{item.node_id}</td>
<td>{item.organizations_url}</td>
<td>{item.received_events_url}</td>
<td>{item.repos_url}</td>
<td>{item.site_admin}</td>
<td>{item.starred_url}</td>
<td>{item.subscriptions_url}</td>
<td>{item.type}</td>
<td>{item.url}</td>
</tr>
))
}
</tbody>
</table>
<Pagination postsPerPage={postsPerPage} totalPosts={userData.length} paginate={paginate} />
</div>
</div>
)
}
export default About
I saw from stackoverflow the sorting example and I copied it to work with the code I have already.
First locate this line
const [sortType, setSortType] = useState('asc');
change it to
const [sortType, setSortType] = useState(null);
Now go to, replace with the code below
const sorted = useMemo(() => {
if(!sortType) return searchPosts;
return searchPosts.slice().sort((a, b) => a[sortType].localeCompare(b[sortType]));
}, [searchPosts, sortType]);
The mistake you did was the searchPosts instead of sortType
Then in your table, go
<th>
login
<a onClick={() => setSortType("login")}> Sort by Login</a>
</th>
You shouldn't have any error.
I'm trying to display these data that is stored in countries list, however, it is result in me getting nothing in my Table. The problem is most likely caused by the Table not rerendering correctly, I tried to use useEffect on line 14 to print out the length of the list, and I did get the correct result. Any hint would be appreciated!
import React, { useState, useEffect } from "react";
import getData from "../Hooks/getData";
import Table from "react-bootstrap/Table";
const Lists = () => {
const [countries, setCountries] = useState([]);
if (countries.length === 0) {
getData().then((data) => {
setCountries(data.data.Countries);
});
}
useEffect(() => {
console.log(countries.length);
});
const getInfo = () => {
countries.map((country) => {
return (
<tr>
<td>{country.Country}</td>
<td>{country.NewConfirmed}</td>
<td>{country.TotalConfirmed}</td>
</tr>
);
});
};
return (
<Table striped bordered hover>
<thead>
<tr>
<th>Country Name</th>
<th>New Confirmed</th>
<th>Total Confirmed</th>
</tr>
</thead>
<tbody>{getInfo()}</tbody>
</Table>
);
};
export default Lists;
Your getInfo does not return anything.
Either use the implicit return, by not using {} around the funtion body, or explicitly use the return statement
const getInfo = () => countries.map((country) => {
return (
<tr>
<td>{country.Country}</td>
<td>{country.NewConfirmed}</td>
<td>{country.TotalConfirmed}</td>
</tr>
);
});
or
const getInfo = () => {
return countries.map((country) => {
return (
<tr>
<td>{country.Country}</td>
<td>{country.NewConfirmed}</td>
<td>{country.TotalConfirmed}</td>
</tr>
);
});
};