I have this table I am working with. So far, when the "Date" column get's clicked, it sorts the table in ascending order just fine. I would like to be able to click it again and have it return to descending order. Any idea on how to do it?
I know that just switching my a and b in sortBy after the => will do the opposite.
class Orders extends React.Component {
constructor(props) {
super(props);
this.state = {
data: [
{orders: 'Vanilla', date: '03/15/1990'},
{orders: 'Chocolate', date: '03/15/1989'},
],
};
this.sortBy.bind(this);
}
renderTableData() {
return this.state.data.map((data, index) => {
const{orders, date} = data
return (
<tr key={index}>
<td>{orders}</td>
<td>{date}</td>
</tr>
)
})
}
sortBy(sortedKey) {
const data = this.state.data;
data.sort((a,b) => a[sortedKey].localeCompare(b[sortedKey]))
this.setState({data})
}
render() {
let newData = this.state.data;
return (
<table id="orders">
<tr className="header">
<th>Order</th>
<th onClick={() => this.sortBy('date')}>Date</th>
</tr>
<tbody>
{this.renderTableData()}
</tbody>
</table>
);
}
}
export default Orders;
first thing you should maintain the sorting order in state:
this.state = {
sortingOrder:'DESC',
data: [
{orders: 'Vanilla', date: '03/15/1990'},
{orders: 'Chocolate', date: '03/15/1989'},
],
};
after each sorting you should alter it in your sort function
sortBy(sortedKey) {
const data = this.state.data;
let sortingOrder=this.state.sortingOrder;
if(sortingOrder==='ASC'){
sortingOrder='DESC';
data.sort((a,b) => b[sortedKey].localeCompare(a[sortedKey]))
}else{
sortingOrder='ASC';
data.sort((a,b) => a[sortedKey].localeCompare(b[sortedKey]))
}
this.setState({data,sortingOrder});
}
inorder to do initial sorting by ascending order you should call sort by on componentDidMount method,note that i have changed ASC to DESC as initial value
componentDidMount(){
this.sortBy('date');
}
Just add another state variable which is boolean and based on that switch the order. Something like this sample
Related
I would like to create an array outside useTable invocation, manipulate the array, and based on the array state make checkbox checked or unchecked. After each click on a button, length of the array is increased by adding one element to the array. When length become greater than 3, the input should be checked.
The problem is that the array state is different inside and outside checked attribute of input. Outside it works as expected: the array length increases. Inside, the array length is equal to initial length of 0.
I have attached code with some logging. I think that the relevant part probably ends with the end of useTable invocation (then is some code which I took from react-table docs with button and mock data, columns added). What changes should I introduce to the code to make it work as I expect?
import React, { useMemo, useState } from 'react';
import { useTable } from 'react-table'
function Table({ columns, data }) {
// neither stateArr nor simpleArr help reach what I would like to
const [stateArr, setStateArr] = useState([]);
let simpleArr = [...stateArr];
const handleOnButtonClick = () => {
console.log("Outside checked: simpleArr, stateArr");
console.log(simpleArr);
console.log(stateArr);
setStateArr([...stateArr, 1]);
// in this case unnecessary, since, as I understand, simpleArr is rendered and (re)assigned above
// simpleArr = [...stateArr];
};
const {
getTableProps,
getTableBodyProps,
headerGroups,
rows,
prepareRow,
} = useTable({
columns,
data,
},
(hooks) => {
hooks.visibleColumns.push((columns) => {
return [
{
id: 'checkedInputs',
Header: () => {
return (<div>
<input type="checkbox"
// working, not most elegant way to combine logging and computing boolean
checked={console.log("Inside checked: simpleArr, stateArr") || console.log(simpleArr)
|| console.log(stateArr) || simpleArr.length > 3 || stateArr.length > 3} />
</div>);
},
Cell: () => {
return (<div>R</div>);
},
},
...columns,
];
});
}
);
return (
<div>
<table {...getTableProps()}>
<thead>
{headerGroups.map(headerGroup => (
<tr {...headerGroup.getHeaderGroupProps()}>
{headerGroup.headers.map(column => (
<th {...column.getHeaderProps()}>{column.render('Header')}</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>
<button type="button" onClick={handleOnButtonClick}>Click Me!</button>
</div>
)
}
function App() {
const columns = useMemo(
() => [
{
Header: 'Animal Type',
accessor: 'animalType',
},
{
Header: 'Number of legs',
accessor: 'numberOfLegs',
},
],
[],
);
const data = useMemo(
() => [
{
animalType: 'dog',
numberOfLegs: 4,
},
{
animalType: 'snake',
numberOfLegs: 0,
},
],
[],
);
return (
<Table columns={columns} data={data} />
)
}
export default App;
Stale Data
The hooks.visibleColumns.push function is called one time when the table is created. It creates a Header render component that takes some props and returns a JSX element. The function which renders the Header based on these props is called every time that the table updates. The function which creates this Header component is called once.
In your example, you create a Header component which prints out some data based on the values of simpleArr and stateArr at the time that it was created, not at the time that it was called.
Table State
If we want our Header component to render with current data then we should get that data from props. The Header is called with quite a lot of props but the one that we will use is state which is the state of the table. We will set the initialState of the table to an object { stateArr: [] }. This gets merged with the standard table state { hiddenColumns: [] }.
The table state is updated through a useReducer hook, so we update it by disptaching an action. We need a custom stateReducer to update the table state based on the contents of the action.
import React, { useMemo } from "react";
import { useTable } from "react-table";
function Table({ columns, data }) {
const {
getTableProps,
getTableBodyProps,
headerGroups,
rows,
prepareRow,
dispatch,
state
} = useTable(
{
columns,
data,
initialState: {
stateArr: []
},
stateReducer: (newState, action, prevState) => {
console.log(action, newState, newState.stateArr);
switch (action.type) {
case "incrementChecks":
return {
...newState,
stateArr: [...newState.stateArr, action.payload]
};
default:
return newState;
}
}
},
(hooks) => {
hooks.visibleColumns.push((columns) => {
return [
{
id: "checkedInputs",
Header: (props) => {
console.log("header props", props); // so you can see all the data you get
console.log("stateArr", props.state.stateArr);
return (
<input
type="checkbox"
readOnly
checked={props.state.stateArr.length > 3}
/>
);
},
Cell: () => {
return <div>R</div>;
}
},
...columns
];
});
}
);
const handleOnButtonClick = () => {
// payload is the item which we are appending to the array
dispatch({ type: "incrementChecks", payload: 1 });
};
console.log("stateArr", state.stateArr);
return (
<div>
<table {...getTableProps()}>
<thead>
{headerGroups.map((headerGroup) => (
<tr {...headerGroup.getHeaderGroupProps()}>
{headerGroup.headers.map((column) => (
<th {...column.getHeaderProps()}>{column.render("Header")}</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>
<button type="button" onClick={handleOnButtonClick}>
Append
</button>
</div>
);
}
function App() {
const columns = useMemo(
() => [
{
Header: "Animal Type",
accessor: "animalType"
},
{
Header: "Number of legs",
accessor: "numberOfLegs"
}
],
[]
);
const data = useMemo(
() => [
{
animalType: "dog",
numberOfLegs: 4
},
{
animalType: "snake",
numberOfLegs: 0
}
],
[]
);
return <Table columns={columns} data={data} />;
}
export default App;
CodeSandbox Link
I am using an API to fetch some data. When the page loads it fetches some random data, but I want to allow the user to sort the data by clicking a button. I have made a function to sort these data from the API I am using. What I want to do now is: When the button to sort data is clicked, I want the new data to be replaced with the old data.
Here is my current code:
class Data extends React.Component {
constructor(props) {
super(props);
this.state = {
data: [],
offset: 0, perPage: 12 // ignore these two
};
}
// The random data that I want to be displayed on page load
receivedData() {
axios
.get(`https://corona.lmao.ninja/v2/jhucsse`)
.then(res => {
const data = res.data;
const slice = data.slice(this.state.offset, this.state.offset + this.state.perPage) // ignore this
const postData = slice.map(item =>
<tr key={Math.random()}>
<td>{item.province}, {item.country}</td>
<td>{item.stats.confirmed}</td>
<td>{item.stats.deaths}</td>
<td>{item.stats.recovered}</td>
</tr>
)
this.setState({
pageCount: Math.ceil(data.length / this.state.perPage), // ignore this
postData
})
});
}
// The data to be sorted when the "country" on the table head is clicked
sortData() {
axios
.get(`https://corona.lmao.ninja/v2/jhucsse`)
.then(res => {
const data = res.data;
var someArray = data;
function generateSortFn(prop, reverse) {
return function (a, b) {
if (a[prop] < b[prop])
return reverse ? 1 : -1;
if (a[prop] > b[prop])
return reverse ? -1 : 1;
return 0;
};
}
// someArray.sort(generateSortFn('province', true))
const tableHead = <tr>
<th onClick={() => someArray.sort(generateSortFn('province', true))}>Country</th>
<th>Confirmed Cases</th>
<th>Deaths</th>
<th>Recovered</th>
</tr>
this.setState({
tableHead
})
});
}
componentDidMount() {
this.receivedData()
this.sortData() // This function should be called on the "Country - table head" click
}
render() {
return (
<div>
<table>
<tbody>
{this.state.tableHead}
{this.state.postData}
</tbody>
</table>
</div>
)
}
}
export default Data;
Think a litte bit different. In the componentDidMount get you're Data in some form. Set it with setState only the raw Data not the html. Then resort the data on button click. React rerenders if the state changes automatically
class Data extends React.Component {
constructor(props) {
super(props);
this.state = {
data
}
}
getData() {
fetchData('url').then(res) {
this.setState({data: res.data})
}
}
componentDidMount() {
this.getData()
}
sort() {
let newSorted = this.state.data.sort //do the sorting here
this.setState({data: newSorted})
}
render() {
return() {
<table>
<tablehead><button onClick={this.sort.bind(this)}/></tablehead>
{this.state.data.map(data => {
return <tablecell>{data.name}</tablecell>
})}
</table>
}
}
}
I am working with a table and I am trying to figure out is there a way to set the default order to ASC when the page loads?
class Orders extends React.Component {
constructor(props) {
super(props);
this.state = {
data: [
{orders: 'Vanilla', date: '03/15/1990'},
{orders: 'Chocolate', date: '03/15/1989'},
],
sortingOrder: 'ASC'
};
this.sortBy.bind(this);
}
renderTableData() {
return this.state.data.map((data, index) => {
const{orders, date} = data
return (
<tr key={index}>
<td>{orders}</td>
<td>{date}</td>
</tr>
)
})
}
sortBy(sortedKey) {
const data = this.state.data;
let sortingOrder = this.state.sortingOrder;
if(sortingOrder === 'ASC') {
sortingOrder = 'DESC';
data.sort((a,b) => b[sortedKey].localeCompare(a[sortedKey]))
}
else {
sortingOrder = 'ASC';
data.sort((a,b) => a[sortedKey].localeCompare(b[sortedKey]))
}
this.setState({data, sortingOrder })
}
render() {
return (
<table id="orders">
<thead>
<tr className="header">
<th>Order</th>
<th onClick={() => this.sortBy('date')}>Date</th>
</tr>
</thead>
<tbody>
{this.renderTableData()}
</tbody>
</table>
);
}
}
I tried calling this.sortBy() in my render method first, but that gave me an error about too many calls. Any ideas?
Error: Maximum update depth exceeded. This can happen when a component repeatedly calls setState inside componentWillUpdate or componentDidUpdate. React limits the number of nested updates to prevent infinite loops.
you can do on componentDidMount for default
componentDidMount(){
this.sortBy('ASC');
}
you should use the comopnentDidMount method
https://reactjs.org/docs/react-component.html#componentdidmount
componentDidMount(){
this.sortBy('date');
}
Im creating a table from JSON data, and main problem is I can't use object keys to map it.
My json something like this:
[{
key: 'val',
key2: 'val',
key3: 'val'
},
{
key: 'val',
key2: 'val',
key3: 'val'
}]
Here is how i implemented header with columns:
class Table extends Component {
render() {
const header = this.props.data.slice(0, 1);
return (<table>
<thead>
<TableHead children={header}/>
</thead>
<tbody>
<TableBody children={this.props.data}/>
</tbody>
</table>)
}
}
export default Table;
class TableHead extends Component {
render() {
return (
<tr>
{this.props.children.map((header) => {
return Object.keys(header).map((el) => {
return <th>{el}</th>
})
})}
</tr>
)
}
}
export default TableHead;
But I can't understand how to map my table body iterating over objects...
I sliced my JSON for header, but I can't do this with data, and my table looks like
class TableBody extends Component {
render() {
const row = this.props.children.map((row) => {
return Object.values(row).map((el,i) => {
if (i%Object.values(row).length===0) {
return <tr><td>{el}</td></tr>
}else{
return <td>{el}</td>
}
})
});
return (
<tbody>
{row}
</tbody>
)
}
}
export default TableBody;
I would extract the keys and re-use when mapping over the rows for the TableBody.
Something like
class Table extends Component {
render() {
const { data } = this.props;
const columns = Object.keys(data[0]);
return (
<table>
<thead>
<TableHead columns={columns} />
</thead>
<tbody>
<TableBody columns={columns} data={data} />
</tbody>
</table>
);
}
}
class TableHead extends Component {
render() {
const { columns } = this.props;
return (
<tr>
{columns.map(header => {
return <th>{header}</th>;
})}
</tr>
);
}
}
class TableBody extends Component {
render() {
const { columns, data } = this.props;
return data.map(row => (
<tr>
{columns.map(cell => (
<td>{row[cell]}</td>
))}
</tr>
));
}
}
I have an array items[] which has four fields name, origin, destination, seats.
I would like to sort the name alphabatically onClick of the table heading i.e Name
heres my snippet of code
JS file with array declaration variables and passing the values to form.js
Stuff.js
import React, { Component } from 'react';
import './App.css';
import Tab from './Table';
import Form from './Form';
class Stuff extends Component {
constructor() {
super();
this.state = {
name: '',
origin: '',
destination: '',
seats: '',
items: []
}
};
handleFormSubmit = (e) => {
e.preventDefault();
let items = [...this.state.items];
items.push({
name: this.state.name,
origin: this.state.origin,
destination: this.state.destination,
seats: this.state.seats
});
this.setState({
items,
name: '',
origin: '',
destination: '',
seats: ''
});
};
handleInputChange = (e) => {
let input = e.target;
let name = e.target.name;
let value = input.value;
this.setState({
[name]: value
})
};
render() {
return (
<div className="App">
<Form handleFormSubmit={ this.handleFormSubmit }
handleInputChange={ this.handleInputChange }
newName={ this.state.name }
newOrigin={ this.state.origin }
newDestination={ this.state.destination }
newSeats={ this.state.seats } />
<Tab items={ this.state.items }/>
</div>
);
}
}
export default Stuff;
Table.js
import React, { Component } from 'react';
import { Table } from 'reactstrap';
class Tab extends React.Component {
render() {
const items = this.props.items;
return (
<Table striped>
<thead>
<tr>
<th >Name</th>
<th>Origin</th>
<th>Destination</th>
<th>Seats</th>
</tr>
</thead>
<tbody>
{items.map(item => {
return (
<tr>
<td>{item.name}</td>
<td>{item.origin}</td>
<td>{item.destination}</td>
<td>{item.seats}</td>
</tr>
);
})}
</tbody>
</Table>
);
}
}
export default Tab;
Is there any way that when I click on Name Heading in my table it would sort according to the name?
Thank You
I don't go into details of ReactJS, you need to implement yourself. The idea is quite simple:
You create a event handler for header like (onClick)="sortByName()"
Define your function sortByName by passing your data into it, and sort it manually, you can use Array.sort() to achieve this.
Update the state will make React re-render the part have the update
I hope this answers your query:
const items = [
{
name: 'C',
quantity: 2
},
{
name: 'B',
quantity: 3
},
{
name: 'A',
quantity: 8
}
];
console.log(items);
items.sort((a,b) => {
const name1 = a.name.toLowerCase(), name2 = b.name.toLowerCase();
return name1 === name2 ? 0 : name1 < name2 ? -1 : 1;
});
console.log(items);
https://jsfiddle.net/abhikr713/r6L2npfL/1/