I'm trying to write a reactjs table for an assignment and am having trouble getting a sorting component working on the columns of my table. Most of this was duplicated from a previous post and I tried to integrate my data into the mold, but I am getting no results. Any idea why the code I have might not be working? Thanks!
class UserTable extends React.Component {
constructor(props){
super(props)
this.state = {
users: this.props.users,
sort: {
column: null,
direction: 'desc',
}
}
}
onSort = (column) => {
return e => {
const direction = this.state.sort.column ? (this.state.sort.direction === 'asc' ? 'desc' : 'asc') : 'desc'
const sortedUsers = this.state.users.sort((a, b) => {
if (column === 'name.first') {
const nameA = a.name.first.toUpperCase() // ignore upper and lowercase
const nameB = b.name.first.toUpperCase() // ignore upper and lowercase
if (nameA < nameB)
return -1
if (nameA > nameB)
return 1
else return 0
}
else {
return a.name.first - b.name.first
}
})
if (direction === 'desc') {
sortedUsers.reverse()
}
this.setState({
users: sortedUsers,
sort: {
column,
direction,
},
})
}
}
const setArrow = (column) => {
let className = 'sort-direction';
if (this.state.sort.column === column) {
className += this.state.sort.direction === 'asc' ? ' asc' : ' desc';
}
return className;
};
render() {
return (
<table>
<thead>
<tr>
<th onClick={this.onSort('name.first')}>
First Name
<span className={this.setArrow('name.first')}></span>
</th>
<th onClick={this.onSort('name.last')}>
Last Name
<span className={this.setArrow('name.last')}></span>
</th>
<th onClick={this.onSort('company')}>
Company
<span className={this.setArrow('company')}></span>
</th>
<th onClick={this.onSort('phone')}>
Phone
<span className={this.setArrow('phone')}></span>
</th>
<th onClick={this.onSort('email')}>
Email
<span className={this.setArrow('email')}></span>
</th>
<th onClick={this.onSort('birthdate')}>
Birthdate
<span className={this.setArrow('birthdate')}></span>
</th>
</tr>
</thead>
<tbody>
{this.state.users.map((user, index) => {
return (
<tr>
<td>{user.name.first}</td>
<td>{user.name.last}</td>
<td>{user.company}</td>
<td>{user.phone}</td>
<td>{user.email}</td>
<td>{user.birthdate}</td>
</tr>
);
})}
</tbody>
</table>
);
}
}
ReactDOM.render(<UserTable users={users} />, document.getElementById('container'));
You should return a function in onSort handler since event handlers take reference of a function but in your case they are getting undefined because you are invoking function and nothing returned.
So you code should looks like this:
onSort = column => {
return e => {
const direction = this.state.sort.column ? (this.state.sort.direction === 'asc' ? 'desc' : 'asc') : 'desc'
const sortedUsers = this.state.users.sort((a, b) => {
if (column === 'name.first') {
const nameA = a.name.first.toUpperCase() // ignore upper and lowercase
const nameB = b.name.first.toUpperCase() // ignore upper and lowercase
if (nameA < nameB)
return -1
if (nameA < nameB)
return 1
else return 0
}
else {
return a.name.first - b.name.first
}
})
if (direction === 'desc') {
sortedUsers.reverse()
}
this.setState({
users: sortedUsers,
sort: {
column,
direction,
},
})
}
}
Related
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 am trying to make a demo invoice with react and firebase. The goal is to autofill the item code and item price after getting the "itemname" value from dropdown. But at the initial change, its still showing the default values of the states. And from the 2nd change of dropdown, its collecting the previous "itemname" and filling "itemprice" and "itemcode" accordingly while in dropdown the itemname is changed.
const [items, setItems]: any = useState([]);
const [itemName, setItemName]:any = useState ("banana");
const [itemUnitPrice, setItemUnitPrice] = useState(10);
const [itemCode, setItemCode] = useState(20349);
const handleItemNameChange = (e:any) => {
e.preventDefault();
console.log("this is e", e);
{ setItemName(e.target.value)}
{items.length != 0 && itemName != undefined && autofillItemDetails(items, itemName)}
}
function autofillItemDetails(itms:any, itmName:string) {
console.log("autofill fuction ran");
let i
for(i = 0; i<items.length; i++){
if (items[i].fruitname === itemName) {
setItemUnitPrice(items[i].price)
setItemCode(items[i].code)
console.log("this is i", i);
console.log("this is fruitname", items[i].fruitname);
console.log("this is price", items[i].price);
// break
}
}
}
useEffect(() => {
getDocs(colRef)
.then((snapshot) => {
let itemsFirebase:any = []
snapshot.docs.forEach((doc) => {
itemsFirebase.push({...doc.data(), id:doc.id});
// console.log("this is itemsfirebase",itemsFirebase)
setItems(itemsFirebase)
})
})
}, [])
console.log("this is items",items
console.log("this is itemname", itemName)
return (
<table width='100%'>
<thead>
<tr>
<td>Item Code</td>
<td>Item Name</td>
<td>Unit Price</td>
<td>Qty</td>
<td>Total</td>
</tr>
</thead>
<tbody>
<tr className="h-10">
<td>{itemCode}</td>
<td>
<select onChange={handleItemNameChange} value={itemName}>
{items.map((item:any) => (
<option key = {item.id}> {item.fruitname} </option>
))}
{/* <option>Grapefruit</option>
<option>Mango</option>
<option>coconut</option>
<option>Litchi</option>
<option>Banana</option> */}
</select>
</td>
<td>{itemUnitPrice}</td>
<td>129</td>
<td>1032</td>
</tr>
</tbody>
</table>
)
}
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 which displays fields of products from an API. I need the rows to appear in ASC; order. I have seen a couple methods on how to do this using React-Tables but I have it so far set up without any of the hooks so I would prefer to a built in method.
export const Table = ({data}) => {
const columns = data[0] && Object.keys(data[0])
return <table cellPadding={0} cellSpacing={0}>
<thead>
<tr>{data[0] && columns.map((heading) => <th>{heading}</th>)}</tr>
</thead>
<tbody>
{data.map(row => <tr>
{
columns.map(column => <td>{row[column]}</td>)
}
</tr>)}
</tbody>
</table>
}
you just have to sort your data array with sort()...
read this:
https://developer.mozilla.org/es/docs/Web/JavaScript/Reference/Global_Objects/Array/sort
an example here =>
Sort array by firstname (alphabetically) in Javascript
Example of a table with 3 sorting states (ascending, descending and unsorted)
const sortArray = (header) => {
const newArr = [...initailArr];
if (sortState === 0) {
newArr.sort((a, b) => {
const keyA = a[header];
const keyB = b[header];
if (keyA < keyB) return -1;
if (keyA > keyB) return 1;
return 0;
});
setSortState(1);
} else if (sortState === 1) {
newArr.sort((a, b) => {
const keyA = a[header];
const keyB = b[header];
if (keyA > keyB) return -1;
if (keyA < keyB) return 1;
return 0;
});
setSortState(2);
} else {
setSortState(0);
}
setSortedArray(newArr);
};
full working example can be found here
https://codesandbox.io/s/async-await-then-study-5yvts?file=/src/Table.js:1483-2126
I'm still new in React. I get some data from a JSON file. I managed to get the search to work. But I also want to be able to click my name table header and filter my data by name. How do I make that work with filter.
import React, { PropTypes } from 'react';
// Mock Data
let MockData = require('./generated.json');
let searchValue = '';
export default class Employees extends React.PureComponent {
constructor(props) {
super(props);
this.state = {
searchValue: '',
sortValue: null
};
this.searchInputChange = this.searchInputChange.bind(this);
this.searchSubmit = this.searchSubmit.bind(this);
}
// Sort function
sortFunction(sortValue, event) {
alert(sortValue);
this.setState({sortValue: sortValue});
}
// Update search value state function
searchInputChange(event) {
this.searchValue = event.target.value;
}
// Search function
searchSubmit(event) {
this.setState({searchValue: this.searchValue});
}
render() {
let sortedEmployeesBySearch = MockData.filter(
(employee) => {
// If state searchValue is not null
if (this.state.searchValue) {
return employee.name.indexOf(this.state.searchValue) !== -1 || employee.gender.indexOf(this.state.searchValue) !== -1 || employee.company.indexOf(this.state.searchValue) !== -1 || employee.email.indexOf(this.state.searchValue) !== -1;
}
else {
return employee;
}
}
);
return (
<div className="container">
<input className="search" type="text" name="search" placeholder="Search table" onChange={this.searchInputChange} />
<input className="searchButton" type="button" value="Search" onClick={this.searchSubmit} />
<table className="employeesList">
<thead>
<tr>
<th onClick={this.sortFunction.bind(this,'name')}>Name</th>
<th onClick={this.sortFunction.bind(this,'gender')}>Gender</th>
<th onClick={this.sortFunction.bind(this,'company')}>Company</th>
<th onClick={this.sortFunction.bind(this,'email')}>E-mail</th>
</tr>
</thead>
<tbody>
{ sortedEmployeesBySearch.map((employee) => (
<tr key={employee.id}>
<td>{employee.name}</td>
<td>{employee.gender}</td>
<td>{employee.company}</td>
<td>{employee.email}</td>
</tr>
))}
</tbody>
</table>
</div>
);
}
}
You can sort the data by:
1) Storing in state the property to sort on, which you are already doing
2) Chaining a sort function after the filter of your MockData
The second task can be accomplished by:
MockData.filter(...).sort((a, b) => {
aVal = a[this.state.sortValue];
bVal = b[this.state.sortValue];
switch(typeof aVal) {
case 'string':
return aVal.localeCompare(bVal);
case 'number':
return aVal - bVal;
default:
throw new Error("Unsupported value to sort by");
}
});
You can even pass a custom function into the sort method that takes the two values and does custom sorting logic based on the sortValue property.