Only want to navigate the mos id row with the help of up and down key but right now only all row mos id, duration navigate with this code.
please help
Code:
` const MouseKeyDown = async (event, id) => {
const actives = document.activeElement;
switch (event.key) {
case "ArrowUp":
return actives?.previousElementSibling?.focus();
case "ArrowDown":
// event.target && event.target.focus();
event.preventDefault();//Scroll With row , not table
return actives?.nextElementSibling?.focus();
default:
break;
}
}
<tbody>
{
dataName.map((value, index) => {
return (
<tr key={index} className="border_bottom"
>
<td onClick={() => CopyClipborad(value.Name)} className="FocusRow"
tabIndex={index} onKeyDown={(e) => { MouseKeyDown(e); }}>{value.Name}</td>
<td>{value.Name}</td>
<td>{value.Name}</td>
</tr>
)
})
}
</tbody>
`
I have a table that has a big database, 2k lines +
So I had a function to filter and organize this working, but it takes 1-2 secs to filter, is it possible to do with useMemo or useCallBack since I always need to redo the database?
export default function Table(props) {
const [order, setOrder] = useState(true);
function handleHeaderClick(clickedHeader) {
setOrder(!order);
const newdata = props.data.sort((a, b) => {
const x = a[clickedHeader];
const y = b[clickedHeader];
if (order && x > y) return -1;
return 1;
});
props.setdata(newdata);
}
function createHeader(header, index) {
if (header.includes("BET") || header.includes("C") || header.includes("CHECK")) {
return (
<th onClick={(e) => handleHeaderClick(header)} key={index}>
{header}
</th>
);
}
}
function createRow(row) {
// ♠♥♦♣
try {
return row.replace(/s/g, "♠").replace(/d/g, "♦").replace(/c/g, "♣");
} catch (e) {
return row;
}
}
function tableRow(row, indexRow) {
return (
<tr key={indexRow}>
{Object.keys(row).map((hd) => {
const value = createRow(props.data[indexRow][hd]);
if (hd.includes("BET") || hd.includes("C") || hd.includes("CHECK")) {
return (
<td className={value[1]} key={hd}>
{value}
</td>
);
}
})}
</tr>
);
}
return (
<table>
<thead>
<tr>{Object.keys(props.data[0]).map((header, indexHeader) => createHeader(header, indexHeader))}</tr>
</thead>
<tbody>{props.data.map((row, indexRow) => tableRow(row, indexRow))}</tbody>
</table>
);
}
I think I have to change something in my handleHeaderClick function, I have already tried some solutions of this type
const test = useMemo(() => handleHeaderClick, [props.data]);
but my code stops working
where's my table
#edit violation on chrome
[Violation] 'setTimeout' handler took 1458ms
#edit2
On firefox works correct
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 using react-table to display fetched data within a table. You also have different buttons within that table to interact with the data such as deleting an entry, or updating its data (toggle button to approve a submitted row).
The data is being fetched in an initial useEffect(() => fetchBars(), []) and then being passed to useTable by passing it through useMemo as suggested in the react-table documentation. Now I can click on the previously mentioned buttons within the table to delete an entry but when I try to access the data (bars) that has been set within fetchBars()it returns the default state used by useState() which is an empty array []. What detail am I missing? I want to use the bars state in order to filter deleted rows for example and thus make the table reactive, without having to re-fetch on every update.
When calling console.log(bars) within updateMyData() it displays the fetched data correctly, however calling console.log(bars) within handleApprovedUpdate() yields to the empty array, why so? Do I need to pass the handleApprovedUpdate() into the cell as well as the useTable hook as well?
const EditableCell = ({
value: initialValue,
row: { index },
column: { id },
row: row,
updateMyData, // This is a custom function that we supplied to our table instance
}: CellValues) => {
const [value, setValue] = useState(initialValue)
const onChange = (e: any) => {
setValue(e.target.value)
}
const onBlur = () => {
updateMyData(index, id, value)
}
useEffect(() => {
setValue(initialValue)
}, [initialValue])
return <EditableInput value={value} onChange={onChange} onBlur={onBlur} />
}
const Dashboard: FC<IProps> = (props) => {
const [bars, setBars] = useState<Bar[]>([])
const [loading, setLoading] = useState(false)
const COLUMNS: any = [
{
Header: () => null,
id: 'approver',
disableSortBy: true,
Cell: (props :any) => {
return (
<input
id="approved"
name="approved"
type="checkbox"
checked={props.cell.row.original.is_approved}
onChange={() => handleApprovedUpdate(props.cell.row.original.id)}
/>
)
}
}
];
const defaultColumn = React.useMemo(
() => ({
Filter: DefaultColumnFilter,
Cell: EditableCell,
}), [])
const updateMyData = (rowIndex: any, columnId: any, value: any) => {
let barUpdate;
setBars(old =>
old.map((row, index) => {
if (index === rowIndex) {
barUpdate = {
...old[rowIndex],
[columnId]: value,
}
return barUpdate;
}
return row
})
)
if(barUpdate) updateBar(barUpdate)
}
const columns = useMemo(() => COLUMNS, []);
const data = useMemo(() => bars, [bars]);
const tableInstance = useTable({
columns: columns,
data: data,
initialState: {
},
defaultColumn,
updateMyData
}, useFilters, useSortBy, useExpanded );
const fetchBars = () => {
axios
.get("/api/allbars",
{
headers: {
Authorization: "Bearer " + localStorage.getItem("token")
}
}, )
.then(response => {
setBars(response.data)
})
.catch(() => {
});
};
useEffect(() => {
fetchBars()
}, []);
const handleApprovedUpdate = (barId: number): void => {
const approvedUrl = `/api/bar/approved?id=${barId}`
setLoading(true)
axios
.put(
approvedUrl, {},
{
headers: {Authorization: "Bearer " + localStorage.getItem("token")}
}
)
.then(() => {
const updatedBar: Bar | undefined = bars.find(bar => bar.id === barId);
if(updatedBar == null) {
setLoading(false)
return;
}
updatedBar.is_approved = !updatedBar?.is_approved
setBars(bars.map(bar => (bar.id === barId ? updatedBar : bar)))
setLoading(false)
})
.catch((error) => {
setLoading(false)
renderToast(error.response.request.responseText);
});
};
const renderTable = () => {
const {
getTableProps,
getTableBodyProps,
headerGroups,
rows,
prepareRow
} = tableInstance;
return(
<table {...getTableProps()}>
<thead>
{headerGroups.map(headerGroup => (
<tr {...headerGroup.getHeaderGroupProps()}>
{headerGroup.headers.map(column => (
<th {...column.getHeaderProps()}>
<span {...column.getSortByToggleProps()}>
{column.render('Header')}
</span>{' '}
<span>
{column.isSorted ? column.isSortedDesc ? ' ▼' : ' ▲' : ''}
</span>
<div>{column.canFilter ? column.render('Filter') : <Spacer/>}</div>
</th>
))}
</tr>
))}
</thead>
<tbody {...getTableBodyProps()}>
{rows.map(row => {
prepareRow(row)
const rowProps = {...row.getRowProps()}
delete rowProps.role;
return (
<React.Fragment {...rowProps}>
<tr {...row.getRowProps()}>
{row.cells.map(cell => {
return (
<td {...cell.getCellProps()}>{cell.render('Cell')}</td>
)
})}
</tr>
{row.isExpanded ? renderRowSubComponent({row}): null}
</React.Fragment>
)})
}
</tbody>
</table>
)
}
}
export default Dashboard;
You're seeing stale values within handleApprovedUpdate because it's capturing bars the first time the component is rendered, then never being updated since you're using it inside COLUMNS, which is wrapped with a useMemo with an empty dependencies array.
This is difficult to visualize in your example because it's filtered through a few layers of indirection, so here's a contrived example:
function MyComponent() {
const [bars, setBars] = useState([]);
const logBars = () => {
console.log(bars);
};
const memoizedLogBars = useMemo(() => logBars, []);
useEffect(() => {
setBars([1, 2, 3]);
}, []);
return (
<button onClick={memoizedLogBars}>
Click me!
</button>
);
}
Clicking the button will always log [], even though bars is immediately updated inside the useEffect to [1, 2, 3]. When you memoize logBars with useMemo and an empty dependencies array, you're telling React "use the value of bars you can currently see, it will never change (I promise)".
You can resolve this by adding bars to the dependency array for useMemo.
const memoizedLogBars = useMemo(() => logBars, [bars]);
Now, clicking the button should correctly log the most recent value of bars.
In your component, you should be able to resolve your issue by changing columns to
const columns = useMemo(() => COLUMNS, [bars]);
You can read more about stale values in hooks here. You may also want to consider adding eslint-plugin-react-hooks to your project setup so you can identify issues like this automatically.
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>
);
});
};