Related
I have a table that is populated with data from an array of objects.
This data is collected via websockets. The socket listens to 3 different events, and does one of the following to the array based on the event:
User can add an entry - ✅ works fully
User can modify an entry - issue
User can delete an entry - ✅ works fully
Here is the code:
interface TableEntry {
address: string;
amount: string;
children?: React.ReactNode;
}
export const TableComponent: FC = () => {
const socket = useContext(SocketContext);
const [entriesArray, setEntriesArray] = useState<TableEntry[]>([]);
const { globalAddress } =
useContext(UserDetailsContext);
useEffect(() => {
socket.on("end", (data) => {
setEntriesArray([]);
});
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
useEffect(() => {
var tempEntries = entriesArray;
socket.on("entry", (data) => {
tempEntries.push({ address: data[0], amount: data[1] });
setEntriesArray(tempEntries);
tempEntries = [];
});
return function () {
socket.off("entry");
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
//Update an entry on request from a user
useEffect(() => {
var tempEntries = entriesArray;
socket.on("updateEntry", (data) => {
const findFunc = (element: TableEntry) => element.address == data[0];
const index = tempEntries.findIndex(findFunc);
tempEntries[index].address = "updated address";
setEntriesArray(tempEntries);
tempEntries = [];
});
return function () {
socket.off("updateEntry");
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
return (
<>
<Box>
<Box
w="427px"
h="450px"
top={"72px"}
right={"60px"}
bg={"rgba(255, 255, 255, 0.08)"}
position={"absolute"}
borderRadius="21"
overflow="hidden"
>
<TableContainer>
<Table variant="simple">
<Thead>
<Tr>
<Th fontFamily={"Orbitron"}>Address</Th>
<Th fontFamily={"Orbitron"}>Amount</Th>
</Tr>
</Thead>
<Tbody>
{entriesArray?.map((entry) => {
return entry.address == globalAddress ? (
<Tr key={entry.address} bg={"rgba(255, 255, 255, 0.05)"}>
<Td fontFamily={"Orbitron"}>{shorten(entry.address)}</Td>
<Td fontFamily={"Orbitron"}>{entry.amount}</Td>
</Tr>
) : (
<Tr key={entry.address}>
<Td fontFamily={"Orbitron"}>{shorten(entry.address)}</Td>
<Td fontFamily={"Orbitron"}>{entry.amount}</Td>
</Tr>
);
})}
</Tbody>
</Table>
</TableContainer>
</Box>
</Box>
</>
);
};
The data updates, but it is not being displayed in the table - how can I make sure the data in the table reflects the update?
I assumed since I am changing the state of entriesArray that forced a rerender - but that doesn't seem to be the case?
React's state is immutable, so you cannot update an array item directly. You need to use map with {...element} for cloning and updating that array item at the same time.
useEffect(() => {
socket.on("updateEntry", (data) => {
const tempEntries = entriesArray.map((element: TableEntry) =>
element.address == data[0]
? { ...element, address: "updated address" } //update the found element
: element //keep the original element
);
setEntriesArray(tempEntries);
tempEntries = [];
});
return function () {
socket.off("updateEntry");
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
Note that, you also can clone the entire array with a spread operator like [...entriesArray] or cloneDeep before updating any element, but it's not preferable because it means you want to render the entire array (even though you want to render the updated element only) which hits performance if you have a huge array.
Hey guys I am using this table to display data and I added a button to each row. How would I be able to hide a row when I click the hide button next to it?
I am aware of a way to do within html elements but not sure how to hide a particular row within a table thats within a loop
Can anyone show me how to accomplish this?
Thank you
import React, { Component } from 'react'
class Table extends Component {
constructor(props) {
super(props) //since we are extending class Table so we have to use super in order to override Component class constructor
this.state = { //state is by default an object
students: [
{ id: 1, name: 'Wasif', age: 21, email: 'wasif#email.com' },
{ id: 2, name: 'Ali', age: 19, email: 'ali#email.com' },
{ id: 3, name: 'Saad', age: 16, email: 'saad#email.com' },
{ id: 4, name: 'Asad', age: 25, email: 'asad#email.com' }
]
}
}
renderTableData() {
return this.state.students.map((student, index) => {
const { id, name, age, email } = student //destructuring
return (
<tr key={id}>
<td>{id}</td>
<td>{name}</td>
<td>{age}</td>
<td>{email}</td>
<td><button>HIDE</button></td>
</tr>
)
})
}
renderTableHeader() {
let header = Object.keys(this.state.students[0])
return header.map((key, index) => {
return <th key={index}>{key.toUpperCase()}</th>
})
}
render() { //Whenever our class runs, render method will be called automatically, it may have already defined in the constructor behind the scene.
return (
<div>
<h1 id='title'>React Dynamic Table</h1>
<table id='students'>
<tbody>
<tr>{this.renderTableHeader()}</tr>
{this.renderTableData()}
</tbody>
</table>
</div>
)
}
}
export default Table
You could add an onClick handler to the button that adds a property that determines the student should be hidden or not.
Notice the onClick={() => this.hideRow(id)} below.
renderTableData() {
return this.state.students.map((student, index) => {
const { id, name, age, email, isHidden } = student; //destructuring
// isHidden will default to undefined if not found on the student object
// user is hidden
if (isHidden === true) {
return null;
}
return (
<tr key={id}>
<td>{id}</td>
<td>{name}</td>
<td>{age}</td>
<td>{email}</td>
<td>
<button onClick={() => this.hideRow(id)}>HIDE</button>
</td>
</tr>
);
});
}
The hideRow method will accept a student id and will add an isHidden: true attribute to the student with that id.
hideRow(id) {
const students = this.state.students.map((student) => {
// not same id? leave as is
if (student.id !== id) {
return student;
}
return { ...student, isHidden: true };
});
this.setState({ students });
}
Now you don't want to display the isHidden column, so you have to update renderTableHeader method to skip that.
renderTableHeader() {
let header = Object.keys(this.state.students[0]);
return header.map((key, index) => {
// notice this
if (key === "isHidden") {
return null;
}
return <th key={index}>{key.toUpperCase()}</th>;
});
}
Add a isVisible key in all objects like
students: [
{ id: 1, name: 'Wasif', age: 21, email: 'wasif#email.com', isVisible: true },
{ id: 2, name: 'Ali', age: 19, email: 'ali#email.com', isVisible: true },
{ id: 3, name: 'Saad', age: 16, email: 'saad#email.com', isVisible: true },
{ id: 4, name: 'Asad', age: 25, email: 'asad#email.com', isVisible: true }
]
Then in your render row function do this
renderTableData() {
return this.state.students.map((student, index) => {
const { id, name, age, email, isVisible } = student
return isVisible ? (
<tr key={id}>
<td>{id}</td>
<td>{name}</td>
<td>{age}</td>
<td>{email}</td>
<td><button>HIDE</button></td>
</tr>
) : null
})
On button/row click update state.
Try this code
import React, { Component } from "react";
class Table extends Component {
constructor(props) {
super(props); //since we are extending class Table so we have to use super in order to override Component class constructor
this.state = {
//state is by default an object
students: [
{ id: 1, name: "Wasif", age: 21, email: "wasif#email.com", toggle: true},
{ id: 2, name: "Ali", age: 19, email: "ali#email.com", toggle: true },
{ id: 3, name: "Saad", age: 16, email: "saad#email.com", toggle: true},
{ id: 4, name: "Asad", age: 25, email: "asad#email.com", toggle: true }
]
};
}
handleClick(index) {
let students = [...this.state.students];
students[index].toggle = !students[index].toggle;
this.setState({ students });
}
renderTableData() {
return this.state.students.map((student, index) => {
const { id, name, age, email, toggle } = student; //destructuring
if (toggle) {
return (
<tr key={id}>
<td>{id}</td>
<td>{name}</td>
<td>{age}</td>
<td>{email}</td>
<`td`>
<button
value={index}
onClick={(e) => this.handleClick(e.target.value)}
>
Hide
</button>
</td>
</tr>
);
} else {
return null;
}
});
}
renderTableHeader() {
let header = Object.keys(this.state.students[0]);
return header.map((key, index) => {
return <th key={index}>{key.toUpperCase()}</th>;
});
}
render() {
//Whenever our class runs, render method will be called automatically, it may have already defined in the constructor behind the scene.
return (
<div>
<h1 id="title">React Dynamic Table</h1>
<table id="students">
<tbody>
<tr>{this.renderTableHeader()}</tr>
{this.renderTableData()}
</tbody>
</table>
</div>
);
}
}
export default Table;
Follow these steps:
Put an onclick on the button
Pass the array as props to the component
On the next component display the array
Add the onclick method to it which is also passed as a props from the main component(Pass id as a parameter)
In the method use a filter array to remove the row of your choice when you click it.
The code is as follow:
https://codesandbox.io/s/modern-tdd-mlmzl?file=/src/components/Table.js
I am trying to develop a very simple dashboard with some information. I'm trying to add in a search filter into my code so that I can narrow my data by names. In many other tutorials, I found that they have common used name.toLowerCase().indexOf(this.state.filterName.toLowerCase()) >= 0 but I just didn't work for me and would like to seek guidance from you guys.
You may also provide feedback on the general structure of my code!
Tables.js
class Table extends Component {
constructor(props){
super(props);
this.state = {
filterName: this.props.filterName,
toShow: 'all',
myArrays: [
{ 'id': 1, 'name': 'Eric', 'email': 'name1#email.com', 'role': 'student', 'isadmin': 'false' },
{ 'id': 2, 'name': 'Amanda', 'email': 'name2#email.com', 'role': 'student', 'isadmin': 'false' },
{ 'id': 3, 'name': 'Brenda', 'email': 'name3#email.com', 'role': 'staff', 'isadmin': 'true' },
{ 'id': 4, 'name': 'Charles', 'email': 'name4#email.com', 'role': 'teacher', 'isadmin': 'true' },
{ 'id': 5, 'name': 'Daimon', 'email': 'name5#email.com', 'role': 'assistant', 'isadmin': 'false' }
]
};
this.toShowAdmin = this.toShowAdmin.bind(this);
}
toShowAdmin(){
if (this.state.toShow === 'all'){
this.setState({ toShow: 'admin' }, () => console.log(this.state.toShow ))
} else {
this.setState({ toShow: 'all' }, () => console.log(this.state.toShow ))
}
}
render(){
let myArrays = []
if (this.state.toShow === 'all'){
myArrays = this.state.myArrays;
} else if (this.state.toShow === 'admin'){
myArrays = this.state.myArrays.filter(row => row.isadmin === 'true')
}
myArrays = this.state.myArrays.filter(row => {
return row.name.toLowerCase().indexOf(this.state.filterName.toLowerCase()) >= 0;
});
return(
<div>
<table className="table">
<thead className="thead-dark">
<tr>
<th scope="col"> name </th>
<th scope="col"> email </th>
<th scope="col"> role </th>
<th scope="col"> isadmin </th>
</tr>
</thead>
<tbody>
{myArrays.map(row=>
<tr key={row.id}>
<td> {row.name} </td>
<td> {row.email} </td>
<td> {row.role} </td>
<td> {row.isadmin} </td>
</tr>
)}
</tbody>
</table>
<button type='button' className='btn btn-primary' onClick={this.toShowAdmin}> Admins </button>
{ this.state.filterName }
</div>
);
}
}
export default Table;
Main.js
class Main extends Component {
constructor(props){
super(props);
this.state = {
filterName: ''
};
this.filterUpdate = this.filterUpdate.bind(this);
}
filterUpdate(value){
this.setState({ filterName: value}, () => console.log(this.state.filterName))
}
render(){
return(
<div>
<Search
filterName={this.state.filterName}
filterUpdate={this.filterUpdate}/>
<Tables
filterName={this.state.filterName}/>
</div>
);
}
}
export default Main;
You have to use componentWillReceiveProps to update your child state with parent props once the child component has been loaded. Add something like this to child component -
componentWillReceiveProps(){
this.setState({filterName: this.props.filterName})
}
See this Stack Over Flow Answer
Otherwise, if your logic allows you to use the prop directly in render, you may also do -
myArrays = this.state.myArrays.filter(row => {
return row.name.toLowerCase().indexOf(this.props.filterName.toLowerCase()) >= 0;
});
edit - use componentDidUpdate()
As rightly pointed out in comments, componentWillReceiveProps() is deprecated. Use componentDidUpdate() instead.
componentDidUpdate(){
this.setState({filterName: this.props.filterName})
}
You should use this.props.filterName directly like this:
myArrays = this.state.myArrays.filter(row => {
return row.name.toLowerCase().indexOf(this.props.filterName.toLowerCase()) >= 0;
});
I'm working on this react table sorting when the user clicks on table header it needs to sort the table, sorting is working but the problem is I'm receiving new data every second through SignalR hub and it sets state udata to new data. When a user clicks on table header it sorts the table but again goes back to the new state changed by new data. And cancells the sorted table back to unsorted.
Is there any way I can keep sorted state and still receive data?
I'm new to react any help would be appreciated
constructor() {
super()
this.state = {
udata: [],
sort: {
column: null,
direction: 'desc',
},
}
}
componentDidMount() {
let connection = new signalR.HubConnectionBuilder()
.withUrl('/signalserver')
.build()
connection
.start()
.then(function() {})
.catch(function(err) {
return console.error(err.toString())
})
connection.on(
'APIChannel',
function(data) {
this.setState({udata: data})
}.bind(this),
)
async function start() {
try {
await connection.start()
console.log('connected')
} catch (err) {
console.log(err)
setTimeout(() => start(), 5000)
}
}
connection.onclose(async () => {
await start()
})
}
onSort(column) {
return function(e) {
let direction = this.state.sort.direction
if (this.state.sort.column === column) {
// Change the sort direction if the same column is sorted.
direction = this.state.sort.direction === 'asc' ? 'desc' : 'asc'
}
// Sort ascending.
const sortedData = this.state.udata.sort((a, b) => {
if (column === 'appName') {
// This sorts strings taking into consideration numbers in strings.
// e.g., Account 1, Account 2, Account 10. Normal sorting would sort it Account 1, Account 10, Account 2.
const collator = new Intl.Collator(undefined, {
numeric: true,
sensitivity: 'base',
})
return collator.compare(a.appName, b.appName)
} else {
return a.contractValue - b.contractValue
}
})
// Reverse the order if direction is descending.
if (direction === 'desc') {
sortedData.reverse()
}
// Set the new state.
this.setState({
udata: sortedData,
sort: {
column,
direction,
},
})
}.bind(this) // Bind "this" again because the onSort function is returning another function.
}
renderItem(item, key) {
const itemRows = [
<tr onClick={clickCallback} key={'row-data-' + key}>
<td>{item.appName}</td>
<td>
<h6 className="text-muted">
<i
className={
'fa fa-circle text-c-' +
(item.appState === 'STARTED' ? 'green' : 'red') +
' f-10 m-r-15'
}
/>
{item.appState}
</h6>
</td>
<td>{item.spaceName}</td>
<td>
<h6 className="text-muted">{item.orgName}</h6>
</td>
<td>
<h6 className="text-muted">
{new Date(item.appUpdatedAt).toLocaleString()}
</h6>
</td>
</tr>,
]
return itemRows
}
render() {
let allItemRows = []
this.state.udata.forEach((item, key) => {
const perItemRows = this.renderItem(item, key)
allItemRows = allItemRows.concat(perItemRows)
})
return (
<Aux>
<Row>
<Table hover responsive>
<thead>
<tr>
<th className="sortable" onClick={this.onSort('appName')}>
{' '}
Account Name
</th>
<th> State</th>
<th> Space</th>
<th> Organization</th>
<th className="sortable" onClick={this.onSort('appUpdatedAt')}>
{' '}
Updated At
</th>
</tr>
</thead>
<tbody> {allItemRows}</tbody>
</Table>
</Row>
</Aux>
)
}
Move the sorting part of the function to new a function:
const sortData = (data, column, direction) => {
// Sort ascending.
const sortedData = data.sort((a, b) => {
if (column === 'appName') {
const collator = new Intl.Collator(undefined, {
numeric: true,
sensitivity: 'base',
})
return collator.compare(a.appName, b.appName)
} else {
return a.contractValue - b.contractValue
}
})
// Reverse the order if direction is descending.
if (direction === 'desc') {
return sortedData.reverse()
}
return sortedData
}
You can use this function in componentDidMount before setting the state with the newData and also in onSort function.
onSort(column) {
return function(e) {
let direction = this.state.sort.direction
if (this.state.sort.column === column) {
// Change the sort direction if the same column is sorted.
direction = this.state.sort.direction === 'asc' ? 'desc' : 'asc'
}
// Sort ascending.
const sortedData = this.sortData(this.state.udata, column, direction)
// Set the new state.
this.setState({
udata: sortedData,
sort: {
column,
direction,
},
})
}.bind(this) // Bind "this" again because the onSort function is returning another function.
}
componentDidMount:
componentDidMount() {
// Code
connection.on(
'APIChannel',
function(data) {
let sortedData = []
if (this.state.sort.column) {
sortedData = this.sortData(data, this.state.sort.column,
this.state.sort.direction)
} else {
sortedData = data
}
this.setState({udata: sortedData})
}.bind(this),
)
// Rest of the code
}
EDIT:
import React, { Component } from "react";
import { Row, Col, Form, Card, Table, Tab, Nav } from "react-bootstrap";
import Aux from "../../hoc/_Aux";
import * as signalR from "#aspnet/signalr";
class Dashboard extends Component {
constructor() {
super();
this.state = {
udata: [],
sysdata: [],
expandedRows: [],
user: "active",
system: "",
data: [],
UserFilters: {
appState: [],
orgName: [],
spaceName: []
},
SysFilters: {
appState: []
},
intervalId: 0, //Scroll on top feature
sort: {
column: null,
direction: "desc"
}
};
}
sortData = (data, column, direction) => {
// Sort ascending.
const sortedData = data.sort((a, b) => {
if (column === 'appName') {
const collator = new Intl.Collator(undefined, {
numeric: true,
sensitivity: 'base',
})
return collator.compare(a.appName, b.appName)
} else {
return a.contractValue - b.contractValue
}
})
// Reverse the order if direction is descending.
if (direction === 'desc') {
return sortedData.reverse()
}
return sortedData
};
componentDidMount() {
let connection = new signalR.HubConnectionBuilder()
.withUrl("/signalserver")
.build();
connection
.start()
.then(function () { })
.catch(function (err) {
return console.error(err.toString());
});
connection.on(
"SBUserBrodcasting",
function (data) {
let sortedData = [];
if (this.state.sort.column) {
sortedData = this.sortData(
data,
this.state.sort.column,
this.state.sort.direction
);
} else {
sortedData = data;
}
this.setState({ udata: sortedData });
}.bind(this)
);
connection.on(
"SBSystemBrodcasting",
function (data) {
this.setState({ sysdata: data });
}.bind(this)
);
async function start() {
try {
await connection.start();
console.log("connected");
} catch (err) {
console.log(err);
setTimeout(() => start(), 5000);
}
}
connection.onclose(async () => {
await start();
});
}
onSort(column) {
return function (e) {
let direction = this.state.sort.direction;
if (this.state.sort.column === column) {
// Change the sort direction if the same column is sorted.
direction = this.state.sort.direction === "asc" ? "desc" : "asc";
}
// Sort ascending.
const sortedData = this.sortData(this.state.udata, column, direction);
// Set the new state.
this.setState({
udata: sortedData,
sort: {
column,
direction
}
});
}.bind(this); // Bind "this" again because the onSort function is returning another function.
}
scrollStep() {
if (window.pageYOffset === 0) {
clearInterval(this.state.intervalId);
}
window.scroll(0, window.pageYOffset - this.props.scrollStepInPx);
}
scrollToTop() {
let intervalId = setInterval(
this.scrollStep.bind(this),
this.props.delayInMs
);
this.setState({ intervalId: intervalId });
}
FilterUserArray = (array, UserFilters) => {
let getValue = value =>
typeof value === "string" ? value.toUpperCase() : value;
const filterKeys = Object.keys(UserFilters);
return array.filter(item => {
// validates all filter criteria
return filterKeys.every(key => {
// ignores an empty filter
if (!UserFilters[key].length) return true;
return UserFilters[key].find(
filter => getValue(filter) === getValue(item[key])
);
});
});
};
FilterSysArray = (array, SysFilters) => {
let getValue = value =>
typeof value === "string" ? value.toUpperCase() : value;
const filterKeys = Object.keys(SysFilters);
return array.filter(item => {
// validates all filter criteria
return filterKeys.every(key => {
// ignores an empty filter
if (!SysFilters[key].length) return true;
return SysFilters[key].find(
filter => getValue(filter) === getValue(item[key])
);
});
});
};
HandleRowClick(rowId) {
const currentExpandedRows = this.state.expandedRows;
const isRowCurrentlyExpanded = currentExpandedRows.includes(rowId);
const newExpandedRows = isRowCurrentlyExpanded
? currentExpandedRows.filter(id => id !== rowId)
: currentExpandedRows.concat(rowId);
this.setState({ expandedRows: newExpandedRows });
}
SpaceRenderFilterList(item, key) {
const itemRows = [
<li key={"li-data-" + key}>
<Form.Check
custom
type="checkbox"
value={item}
id={"SBSpace-" + item}
label={item}
onChange={this.UserAppSpaceFilter.bind(this)}
/>
</li>
];
return itemRows;
}
OrgRenderFilterList(item, key) {
const itemRows = [
<li key={"li-data-" + key}>
<Form.Check
custom
type="checkbox"
value={item}
id={"SBOrg-" + item}
label={item}
onChange={this.UserAppOrgFilter.bind(this)}
/>
</li>
];
return itemRows;
}
RenderItem(item, key) {
const clickCallback = () => this.HandleRowClick(key);
const itemRows = [
<tr onClick={clickCallback} key={"row-data-" + key}>
<td>{item.appName}</td>
<td>
<h6 className="text-muted">
<i
className={
"fa fa-circle text-c-" +
(item.appState === "STARTED" ? "green" : "red") +
" f-10 m-r-15"
}
/>
{item.appState}
</h6>
</td>
<td>{item.spaceName}</td>
<td>
<h6 className="text-muted">{item.orgName}</h6>
</td>
<td>
<h6 className="text-muted">
{new Date(item.appUpdatedAt).toLocaleString()}
</h6>
</td>
</tr>
];
if (this.state.expandedRows.includes(key)) {
itemRows.push(
<tr key={"row-expanded-" + key}>
<td colSpan="6">
<Card className="card-event">
<Card.Body>
<div className="row align-items-center justify-content-center">
<div className="col">
<h5 className="m-0">Upcoming Event</h5>
</div>
<div className="col-auto">
<label className="label theme-bg2 text-white f-14 f-w-400 float-right">
34%
</label>
</div>
</div>
<h2 className="mt-2 f-w-300">
45<sub className="text-muted f-14">Competitors</sub>
</h2>
<h6 className="text-muted mt-3 mb-0">
You can participate in event{" "}
</h6>
<i className="fa fa-angellist text-c-purple f-50" />
</Card.Body>
</Card>
</td>
</tr>
);
}
return itemRows;
}
onClickfn = () => {
this.setState({ user: "active", system: "inactive" });
};
onClickfnsys = () => {
this.setState({ user: "inactive", system: "active" });
};
UserAppStateFilter(e) {
let index;
// current array of options
const options = this.state.UserFilters.appState;
// check if the check box is checked or unchecked
if (e.target.checked) {
// add the numerical value of the checkbox to options array
options.push(e.target.value);
} else {
// or remove the value from the unchecked checkbox from the array
index = options.indexOf(e.target.value);
options.splice(index, 1);
}
// update the state with the new array of options
this.setState({
UserFilters: { ...this.state.UserFilters, appState: options }
});
}
UserAppSpaceFilter(e) {
let index;
// current array of options
const options = this.state.UserFilters.spaceName;
// check if the check box is checked or unchecked
if (e.target.checked) {
// add the numerical value of the checkbox to options array
options.push(e.target.value);
} else {
// or remove the value from the unchecked checkbox from the array
index = options.indexOf(e.target.value);
options.splice(index, 1);
}
// update the state with the new array of options
this.setState({
UserFilters: { ...this.state.UserFilters, spaceName: options }
});
}
UserAppOrgFilter(e) {
let index;
// current array of options
const options = this.state.UserFilters.orgName;
// check if the check box is checked or unchecked
if (e.target.checked) {
// add the numerical value of the checkbox to options array
options.push(e.target.value);
} else {
// or remove the value from the unchecked checkbox from the array
index = options.indexOf(e.target.value);
options.splice(index, 1);
}
// update the state with the new array of options
this.setState({
UserFilters: { ...this.state.UserFilters, orgName: options }
});
}
SysAppStateFilter(e) {
let index;
// current array of options
const options = this.state.SysFilters.appState;
// check if the check box is checked or unchecked
if (e.target.checked) {
// add the numerical value of the checkbox to options array
options.push(e.target.value);
} else {
// or remove the value from the unchecked checkbox from the array
index = options.indexOf(e.target.value);
options.splice(index, 1);
}
// update the state with the new array of options
this.setState({
SysFilters: { ...this.state.SysFilters, appState: options }
});
}
render() {
let Spacefilterlist = [];
Array.from(new Set(this.state.udata.map(item => item.spaceName))).forEach(
(item, key) => {
const perItemRows = this.SpaceRenderFilterList(item, key);
Spacefilterlist = Spacefilterlist.concat(perItemRows);
}
);
let Orgfilterlist = [];
Array.from(new Set(this.state.udata.map(item => item.orgName))).forEach(
(item, key) => {
const perItemRows = this.OrgRenderFilterList(item, key);
Orgfilterlist = Orgfilterlist.concat(perItemRows);
}
);
let allItemRows = [];
this.FilterUserArray(this.state.udata, this.state.UserFilters).forEach(
(item, key) => {
const perItemRows = this.RenderItem(item, key);
allItemRows = allItemRows.concat(perItemRows);
}
);
let sysallItemRows = [];
this.FilterSysArray(this.state.sysdata, this.state.SysFilters).forEach(
(item, key) => {
const perItemRows = this.RenderItem(item, key);
sysallItemRows = sysallItemRows.concat(perItemRows);
}
);
return (
<Aux>
<Row>
<Col sm={12}>
<Tab.Container defaultActiveKey="user">
<Row>
<Col sm={2}>
<Nav variant="pills" className="flex-column">
<Nav.Item>
<Nav.Link eventKey="user" onClick={this.onClickfn}>
User
</Nav.Link>
</Nav.Item>
<Nav.Item>
<Nav.Link eventKey="system" onClick={this.onClickfnsys}>
System
</Nav.Link>
</Nav.Item>
</Nav>
<br />
<Card
style={{
display: this.state.user === "active" ? "" : "none"
}}
>
<Tab.Pane eventKey="user">
<Card.Header>
<Card.Title as="h5">Filters</Card.Title>
</Card.Header>
<Card.Body>
<h6>By State</h6>
<hr />
<ul className="list-inline m-b-0">
<Form.Group onReset={this.handleFormReset}>
<li>
<Form.Check
custom
type="checkbox"
id="checkbox1"
value="STARTED"
label="STARTED"
onChange={this.UserAppStateFilter.bind(this)}
/>
</li>
<li>
<Form.Check
custom
type="checkbox"
id="checkbox2"
value="STOPPED"
label="STOPPED"
onChange={this.UserAppStateFilter.bind(this)}
/>
</li>
</Form.Group>
</ul>
<h6>By Space</h6>
<hr />
<ul className="list-inline m-b-0">
<Form.Group>{Spacefilterlist}</Form.Group>
</ul>
<h6>By Organization</h6>
<hr />
<ul className="list-inline m-b-0">
<Form.Group>{Orgfilterlist}</Form.Group>
</ul>
</Card.Body>
</Tab.Pane>
</Card>
<Card>
<Tab.Pane
eventKey="system"
style={{
display: this.state.system === "active" ? "" : "none"
}}
>
<Card.Header>
<Card.Title as="h5">Filters</Card.Title>
</Card.Header>
<Card.Body>
<h6>By State</h6>
<hr />
<ul className="list-inline m-b-0">
<Form.Group>
<li>
<Form.Check
custom
type="checkbox"
id="chec1"
value="STARTED"
label="STARTED"
onChange={this.SysAppStateFilter.bind(this)}
/>
</li>
<li>
<Form.Check
custom
type="checkbox"
id="chec2"
value="STOPPED"
label="STOPPED"
onChange={this.SysAppStateFilter.bind(this)}
/>
</li>
</Form.Group>
</ul>
</Card.Body>
</Tab.Pane>
</Card>
</Col>
<Col sm={10}>
<Tab.Content>
<Tab.Pane eventKey="user">
<Table hover responsive>
<thead>
<tr>
<th
className="sortable"
onClick={this.onSort("appName")}
>
Account Name
</th>
<th>State</th>
<th>Space</th>
<th>Organization</th>
<th
className="sortable"
onClick={this.onSort("appUpdatedAt")}
>
Updated At
</th>
</tr>
</thead>
<tbody>{allItemRows}</tbody>
</Table>
</Tab.Pane>
<Tab.Pane eventKey="system">
<Table hover responsive>
<thead>
<tr>
<th>App Name</th>
<th>State</th>
<th>Space</th>
<th>Organization</th>
<th>Updated At</th>
</tr>
</thead>
<tbody>{sysallItemRows}</tbody>
</Table>
</Tab.Pane>
</Tab.Content>
</Col>
</Row>
</Tab.Container>
</Col>
<button
id="myBtn"
title="Back to top"
className="scroll"
onClick={() => {
this.scrollToTop();
}}
>
<span className="feather icon-chevron-up" />
</button>
</Row>
</Aux>
);
}
}
export default Dashboard;
Use a parent component to perform the request and pass the unsorted values and sorting order value to a child component.
child component(table component most likely) will display data based on sorting order value.
Currently your component get mounted each time you change the state values
Add a life cycle method called componentWillReceiveProps(nextProps) or you can as well use static getDerivedStateFromProps(props, state) to perform the sorting inside of this method, that way when new data is available it will automatically be sorted alongside with the original one that was there. hence all your other codes remain the same and the new data just takes it rightful place in the sorting.
I'd like to sort table items (alphabetical) by clicking on table header. I've tried to do it myself, but it works really strange, only clicking on second header (priority) works... And when I click on first and third header, it sorts table items in order how they were put in there.
I use orderBy from lodash.
Here is my code, the slice of the full class.
const header = [
{name: "Task Name", id: "taskName"},
{name: "Priority", id: "priority"},
{name: "Done", id: "done"},
];
<TableHead>
<TableRow>
{header.map((el, i) => (
<TableCell key={i}>
<div
style={{
display: 'flex',
alignItems: 'center'
}}
onClick={() => this.props.handleSort(el.id)}
>
{el.name}
{
this.props.columnToSort === el.id
? (this.props.sortDirection === 'asc'
? <UpArrow/>
: <DownArrow/>
)
: null
}
</div>
</TableCell>
))}
<TableCell/>
</TableRow>
</TableHead>
And logics in different class, "connected" by props.
const invertDirection = {
asc: "desc",
desc: "asc",
};
class...
state = {
columnToSort: '',
sortDirection: 'desc',
};
handleSort = (columnName) => {
this.setState({
columnToSort: columnName,
sortDirection:
this.state.columnToSort === columnName
? invertDirection[this.state.sortDirection]
: 'asc',
});
};
props
tableData={orderBy(
this.state.tableData,
this.state.columnToSort,
this.state.sortDirection
)}
handleSort = {this.handleSort}
columnToSort = {this.state.columnToSort}
sortDirection = {this.state.sortDirection}
I know it may be hard to read, because I've got many components, but pasted only things I use to do a sort.
Can you tell me why when clicking on second table header priority, sorting works, and when clicking on other headers it don't?
If you have any better ideas for sorting, please let me know.
I'm hopping i understand your goal here, you are trying to sort the data via a click on the table's headers and toggle it to sort it in ascending or descending manner.
If this is correct i would take a simpler approach.
Sorting by dynamic key
You can create a Th component of your own that will take an onClick prop and an id prop where the id is the name of the object's key.
When the Th is clicked it will invoke the handler and will pass the id (the object's key) to the handler.
This way you can sort on the key that got passed by the child.
Ascending Or Descending
We only have 2 options for sorting - Ascending or Descending. This means we can use a Boolean instead of a string (that will simplify our logic a bit).
So after each click on a given Th we will set a new Boolean object in our state when the key being the id of the Th and we will flip it's value.
This way we can conditionally sort by the given key either in an ascending or descending way.
Here is a small running example:
const data = [
{ name: 'John', age: 32 },
{ name: 'Mike', age: 27 },
{ name: 'Jane', age: 31 },
{ name: 'Criss', age: 25 },
{ name: 'Tom', age: 18 },
]
class Th extends React.Component {
handleClick = () => {
const { onClick, id } = this.props;
onClick(id);
}
render() {
const { value } = this.props;
return (
<th onClick={this.handleClick}>{value}</th>
)
}
}
class App extends React.Component {
state = {
users: data
}
handleSort = (id) => {
this.setState(prev => {
return {
[id]: !prev[id],
users: prev.users.sort((a, b) => prev[id] ? a[id] < b[id] : a[id] > b[id] )
}
});
}
render() {
const { users } = this.state;
return (
<table>
<thead>
<tr>
<Th onClick={this.handleSort} id="name" value="Name" />
<Th onClick={this.handleSort} id="age" value="Age" />
</tr>
</thead>
<tbody>
{
users.map(user => (
<tr>
<td>{user.name}</td>
<td>{user.age}</td>
</tr>
))
}
</tbody>
</table>
);
}
}
ReactDOM.render(<App />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>