Im trying to figure out a way to sort my rows array depending on the sortBy and order state. I currently have a handleSort function which is grabbing the column name and setting it to sortBy state and also toggling the order by either "asc" or "desc" but now I'm trying to figure out how to manipulate the rows depending on the sortBy and order state. I believe it's possible creating a long conditional rendering but wondering does anyone one have simpler way I might be missing. Your help is appreciated thank you.
state = {
columnHeaders: [
"Meat",
"Protein (g)",
"Calories (cal)",
"Carbohydrates (g)",
"Fat (g)"
],
rows: [
["chicken breast", "25", "200", "37", "8"],
["fried chicken", "45", "450", "21", "16"],
["baked fish", "15", "250", "30", "9"]
],
sortedBy: "",
order: "desc",
query: "all",
error: false
};
handleClose = () => {
this.setState({ error: !this.state.error });
};
handleQuery = keyword => {
this.setState({
query: keyword
});
if (keyword === "chicken") {
this.setState({
rows: this.state.rows.filter(row => row[0].includes("chicken"))
});
} else if (keyword === "fish") {
this.setState({
rows: this.state.rows.filter(row => row[0].includes("fish"))
});
} else if (keyword === "beef") {
this.setState({
rows: this.state.rows.filter(row => row[0].includes("beef"))
});
} else {
this.setState({
error: true
});
}
};
handleSort = header => {
this.setState(state => ({
sortedBy: header,
order: state.sortedBy === header ? invertDirection[state.order] :
"asc"
}));
};
render() {
const { columnHeaders, rows } = this.state;
return (
<div className="App mt-3">
<AlertDialogSlide
open={this.state.error}
handleClose={this.handleClose}
/>
<Search handleQuery={this.handleQuery} />
<Paper className="mt-3">
<Header />
<Table>
<TableHead>
<TableRow>
{columnHeaders.map((header, i) => (
<TableHeader
header={header}
key={`th-${i}`}
handleSort={this.handleSort.bind(this, header)}
/>
))}
</TableRow>
</TableHead>
<TableBody>
{rows.map((row, i) => (
<TableRow key={`thc-${i}`}>
<TableItem row={row} />
</TableRow>
))}
</TableBody>
</Table>
</Paper>
</div>
);
}
}
First of all, you should save the original data and the filtered data in different locations - e.g. the original data comes from the props and the filtered data is in the state. Because if you overwrite the data (like you do at the moment), you won't be able to e.g. search for "fish" after you've searched for "chicken", because the data was filtered to only include "chicken"-entries - all "fish" entries where removed and are not accessible anymore.
Second, if you want to set a new state depending on an old state, you should always provide a function instead of the state object to the setState function (check out this link).
Third, instead of using the if-else blocks in handleQuery, you can just use the keyword directly to filter.
And now to your question:
You can use the following code snippet to order and filter your rows:
const { rows } = this.props; // assuming the original data comes from the props!
const { query, sortedBy, order } = this.state;
// filter the data (only if query is not "all"
const newRows = query === "all" ? rows : rows.filter(row => row[0].includes(query));
const sortedRows = sortedBy === "" ? newRows : newRows.sort((a, b) => {
const valueA = a[sortedBy]; // get the row to sort by
const valueB = b[sortedBy]; // get the row to sort by
let sortedValue = 0;
if (valueA < valueB) {
sortedValue = -1;
}
else if (valueA > valueB) {
sortedValue = 1;
}
if (order === "desc") {
sortedValue *= -1; // if descending order, turn around the sort order
}
return sortedValue;
});
this.setState({rows: sortedRows});
Related
Need you with regards to deleting the element in an array (lets concentrate in the selectedCategory[]), When i tick the checkbox the return wil be the below
["A", "B", "C", "D"]
My problem now is when I untick my selected category (for example, B), the return will be empty.
My expected result would be like:
["A", "C", "D"]
And depends on what category i perform the unticking and will not delete all from category.. e.g:
["A", "B", "B", "C", "D", "D", "D"]
should delete single "B" only
state = {
selectedColor: [],
selectedCategory: [],
}
onlclickedSelectColor = (event, category) => {
const {selectedColor} = this.state;
let color = event.target.value !== this.state.selectedColor
? event.target.value : '';
//UNTICK
if (selectedColor.find((data)=> data === color)) {
let filteredArray = selectedColor.filter(item => item !== color)
this.setState({
selectedColor: filteredArray,
selectedCategory : selectedCategory .slice(category, 1) //this is the problem, category could be "A"..ect..
});
}
//TICKING
else {
this.setState({
selectedColor : selectedColor.concat(color),
selectedCategory : selCategory.concat(category)
}, () => { this.checkMandatoryFields(); });
}
}
render () {
return (
// this map is from backend
{colors.map((clr, index) => (
<TableRow hover tabIndex={-1}>
<TableCell key={`cell-${index}`} style={{ paddingRight: 0, paddingLeft: 0 }}>
<Checkbox
checked={selectedColor.includes(color[`colorID`])}
onChange={(e) => {this.onlclickedSelectColor(e, category)}}
/>
</TableCell>
</TableRow>
), this)}
)
}
There is no variable initialized, called "category".
You get only one parameter from the "onlclickedSelectColor" function.
// add category parameter
onlclickedSelectColor = (event, category) => {
...
}
There seems to be several things here.
Checkbox is missing a value prop.
string value on checkbox is compared to an array in state (since your accessing the dom value property (e.target.value). so color will always be ' ' and so untick will never enter a true state that actually executes the code.
There is no variable definition for category so the in1ClickedSelectColor is always sent with (e, undefined).
Hope this helps you on your way.
If you struggle further, it would be most helpful if you create a snippet that reproduces your issue.
Using React, I have a list component that uses array.map to render a list of items.
The list items are variegated; every other list item has a different background color which depends on if the id field of the data structure that feeds the list item is even or odd:
...
const useStyles = makeStyles((theme) => ({
even: {
backgroundColor: theme.palette.background.paper,
},
odd: {
backgroundColor: "#c8c9c7",
},
}));
...
const classes = useStyles();
...
{!list || list.length < 1 ? (
<p>You have no assets selected...</p>
) : (
list.map((items) => (
<ListItem
className={items.id % 2 === 0 ? classes.even : classes.odd}
key={items.id}
>
...
/>
</ListItem>
))
)}
Here is an example of the data structure it uses:
{
{
"id":0,
"foo":"This is a bar"
},
{
"id":1,
"foo":"This is also a bar"
},
{
"id":2,
"foo":"Yes, this too, is a bar"
}
}
I need to remove items. Normal javascript.filter produces non contiguous ids as expected:
{
{
"id":0,
"foo":"This is a bar"
},
{
"id":2,
"foo":"Yes, this too, is a bar"
}
}
I need them to be contiguous:
{
{
"id":0,
"foo":"This is a bar"
},
{
"id":1,
"foo":"Yes, this too, is a bar"
}
}
I have a function that does what I need that needs some tweaking:
const handleRemoveAsset = (id) => {
const arrayCopy = [...assetListItems];
const filteredArray = arrayCopy
.filter((item) => item.id !== id)
for (var i=0; i < filteredArray.length; i++) {
filteredArray[i].id = i;
}
setAssetListItems(filteredArray);
};
This works, but one does not simply for loop using React... I am hoping to use filter and/or map for the entirety of this and not use the for loop that I have.
I read that you can chain filter and map and tried it but couldn't quite work it out. I came up with this:
const filteredArray = array
.filter((item) => item.id !== id)
.map((item, index) => {
item && item.id ? item.id : index)});
... which fails to compile with - expected an assignment to a function call and instead saw an expression on the line after .map.
Any advice at this point would appreciated, thank you!
You could chain map and filter and return the new object from map which updates the pre-existing id.
[...assetListItems]
.filter(item => item.id !== id)
.map((item, index) => ({
...item,
id: index,
}));
I just considered another scenario where if the id is not starting with 0. And if you want the starting id in the resultant array to be as the id of the first object then this is just another way of achieving the expected output.
let data = [{id:0, foo:'This is a bar'},{id:1, foo:'This is also a bar'},{id:2, foo:'Yes, this too, is a bar'}];
const filterItems = (items, id) => {
let lastPushedId = items[0]?.id;
return items.filter(item => item.id !== id).map(item => ({
...item,
id: lastPushedId++
}))
}
console.log(filterItems(data, 1));
//`id` of the first object is `3`
data = [{id:3, foo:'This is a bar'},{id:4, foo:'This is also a bar'},{id:5, foo:'Yes, this too, is a bar'}];
console.log(filterItems(data, 3));
.as-console-wrapper {
max-height: 100% !important;
}
I am facing issue while trying to display sorted suggestions with react-bootstrap-typeahead.
JSON
{
"Code": "ABC",
"Name": "Random City, Town Office (ABC), Random Country",
"CityName": "Random City",
"CityCode": "ABC",
"CountryName": "Random Country",
"CountryCode": "XY",
"Field": "Town Office"
},
{
"Code": "CBA",
"Name": "Random City, Town Office (CBA), Random Country",
"CityName": "City",
"CityCode": "CBA",
"CountryName": "Country",
"CountryCode": "CC",
"Field": "Town Office"
}
The desired output should be searched by city alphabetically if it matches, if not should search for country name and then the output rendered should be sorted alphabetically.
I tried pushing callback data from typeahead's filterBy to an array and sorting it, but, since JSON from service is not sorted and callback data is random, unable to achieve the same.
Is there any other way to achieve the same?
<Typeahead
{...this.state.typeProps}
labelKey="Name"
placeholder="Enter Origin..."
bsSize="large"
onChange={(selected) => {
this.setState({ selected });
}}
filterBy={(option, props) => {
if (this.filterAndPush(option, props)) {
return true;
}
return false;
}}
}}
options={this.state.originData}
selected={this.state.selected}
/>
filterData = (option, props) => {
const { text } = props;
const { CityName, CityCode } = option;
if (text) {
if (CityName.toLowerCase().includes(text.toLowerCase())) {
return true;
} else if (CityCode.toLowerCase().includes(text.toLowerCase())) {
return true;
}
return false;
}
return false;
}
filterAndPush = (option, props) => {
//debugger;
if (this.filterData(option, props)) {
debugger;
this.filterResultSet.push(option);
this.filterResultSet.sort((a, b) => {
if (a.CityName < b.CityName)
return -1;
if (a.CityName > b.CityName)
return 1;
return 0;
});
return true;
}
return false;
}
There are two ways you can sort your data:
1) Pre-sort the options before passing them into the options prop. Given your scenario, this is probably the easiest approach. In your render method, simply do the following:
const options = this.state.originData.sort(sortCallback);
return (
<Typeahead
...
options={options}
/>
);
2) Sort the filtered results using renderMenu. Check out the custom menu example to see this in action. Basically, you can pass a callback that receives the filtered results and render them however you want:
_renderMenu(results, menuProps) {
const items = results.sort(sortCallback);
return (
<Menu {...menuProps}>
{items.map((item, index) => {
<MenuItem key={index} option={item} position={index}>
{...}
</MenuItem>
})}
</Menu>
);
}
render() {
return (
<Typeahead
...
renderMenu={this._renderMenu}
/>
);
}
Sorry for the bad title, but I'm not sure how to google this issue. For what ever reason, I'm having trouble wrapping my head around about how to create this table in reactjs. Where XX under column1 is empty while the data in column two will continue to populate.
Pretty much this is what I want: where xx is blank
column 1 column 2
1. David Male
xx 30
xx BasketBall
2. Sam BaseBall
3. John Male
xx Football
I'm trying to do this with reactjs and material-ui.
my data format :
[
['David', ['Male', '30', 'BasketBall']],
['Sam', ['BaseBall']],
['John', ['Male', 'FootBall']]
]
This is a snippet of my code:
<TableBody>
//this will display 1 row for 1 item as in number 2 mentioned above.
{data.map((prop, key) => {
if (prop[1].length <= 1) {
return (
<TableRow key={key}>
{prop.map((prop, key) => {
return (
<TableCell className={classes.tableCell} key={key}>
{prop}
</TableCell>
);
})}
</TableRow>
);
} else {
//this is where I'm stuck at
return (
<TableRow key={key}>
{prop.map((prop, key) => {
return (
<TableCell className={classes.tableCell} key={key}>
{prop}
</TableCell>
);
})}
</TableRow>
);
}
})}
</TableBody>
One solution is to check if the value is an array. If so map each array value into a div (to display them line-by-line). Else, just output prop.
I've added an inline style for vertical-align: top however you may want to move that to your CSS file.
//this is where I'm stuck at
return (
<tr key={key}>
{prop.map((prop, key) => (
<td
key={key}
style={{verticalAlign: 'top'}}
>
{Array.isArray(prop) && prop.map((prop, key) => (
<div key={key}>{prop}</div>
))
|| prop}
</td>
))}
</tr>
);
I would transform my data so it's ready to be rendered without much trouble.
Here's how I would do it:
const tabularizeData = data =>
data.reduce((acc, item, index) => {
let curName;
const name = item[0]
const stuff = item[1]
for (const i in stuff) {
const thing = stuff[i]
if (i > 0) {
acc.push(['', '', thing])
} else {
acc.push([index, name, thing])
}
}
return acc
}, [])
Which would transform your data like this:
const data = [
['David', ['Male', '30', 'BasketBall']],
['Sam', ['BaseBall']],
['John', ['Male', 'FootBall']]
]
tabularizeData(data)
[
[0, "David", "Male"]
["", "", "30"]
["", "", "BasketBall"]
[1, "Sam", "BaseBall"]
[2, "John", "Male"]
["", "", "FootBall"]
]
And now creating your table should be easy. Hope this helps!
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>