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.
Related
I'd like to have a map using OpenStreetMap showing location of flowers. My point is to have LayersControl with colours and type and be able to check for example Orange and Tulip and see only orange tulips on the map but it seems hard from what i read on React LeafLet documentation.
To be easier to understand i will add some code :
Flower datas example:
const flowers = [
{
"type": "Tulip",
"colour": "Orange",
"latlng": [52.081222, 5.235965],
},
{
"type": "Crocus",
"colour": "Red",
"latlng": [52.081421, 5.235534],
},
]
LeafletMap.jsx (Partial):
const LeafletMap: React.FC = () => {
return (
<MapContainer id="mapId"
center={averagePos}
zoom={zoom}>
{flowerItems.map((flower, index) => (
//Maybe do something here like sorting and creating
//multiple layers
//or having already a layer for every type of search
//but if i want to add more colours ou type it can be very hard to update
))}
</LayersControl>
</MapContainer>
)
}
You could maintain the selected layers using state variables - one array to track the type (Crocus or Tulip) and another to track the colour (red or orange). Initially, the states are setup to show everything:
const [type, setType] = useState(['Tulip', 'Crocus']);
const [colour, setColour] = useState(['Orange', 'Red']);
For each type and colour you could create a LayersControl where you hook into the add and remove event handlers and update the corresponding state (type or colour) when the layer is checked or unchecked by the user:
<LayersControl.Overlay name="Crocus" checked="true">
<LayerGroup
eventHandlers={{
add: (e) => {
updateTypeState('Crocus', true);
},
remove: (e) => {
updateTypeState('Crocus', false);
},
}}
></LayerGroup>
</LayersControl.Overlay>
The functions to update the state look like this (there is one function for updating type and one for updating colour):
const updateTypeState = (key: string, value: boolean) => {
setType((prevState) => {
if (value) {
prevState = [...prevState, key];
} else {
prevState = prevState.filter((e) => e !== key);
}
console.log(prevState);
return prevState;
});
};
const updateColourState = (key: string, value: boolean) => {
setColour((prevState) => {
if (value) {
prevState = [...prevState, key];
} else {
prevState = prevState.filter((e) => e !== key);
}
return prevState;
});
};
You could then render the markers using the map function as you suggested:
{flowers
.filter((flower) => {
if (type.length == 0 && colour.length == 0) {
// No layers selected, so show all
return true;
}
if (type.length > 0 && colour.length > 0) {
// Colours and types selected
return (
type.indexOf(flower.type) >= 0 &&
colour.indexOf(flower.colour) >= 0
);
}
if (type.length > 0) {
// Type selected, no colour selected
return type.indexOf(flower.type) >= 0;
}
// Colour selected, no type selected
return colour.indexOf(flower.colour) >= 0;
})
.map((flower, index) => (
<Marker position={flower.latlng}>
<Popup>
{flower.type}, {flower.colour}
</Popup>
</Marker>
))}
There's a working StackBlitz here, but please note the marker images do not display properly due to this issue, but you should be able to see a broken image icon and click on it to view details of the flower.
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 have an array of json object which contains title and array of subtitles and i have a select option where am storing the title lists and i made a loop on the subtitles array so i can add inputs depending on the subtitles length the problem is that when i select first item it works fine but when i select the second item from the dropdown list it doesn't see the new update of the array so it shows me an error of undefined because the state didn't update correctly i didn't know how to solve it
here is my array :
constructor(props) {
super(props);
this.state = {
Modules_SubModules_Array: [{
"Module": "",
"SubModules": []
}],
};
}
and then there is the function that i execute when i select item from dropdown list :
Affect_Module_Submodule = (currentModuleTitle, index) => {
if (
this.state.Modules_SubModules_Array.findIndex(
item => item.Module == currentModuleTitle
) < 0
) {
this.state.Modules_SubModules_Array.splice(index, 1, {
Module: currentModuleTitle,
SubModules: []
});
this.setState({
Modules_SubModules_Array: this.state.Modules_SubModules_Array
});
}
};
and there is the loop that i use :
values.Modules_SubModules_Array[this.state.selectedIndex].SubModules.map(
(subModule, index2) => {
return (
<div key={index2}>
<TextFields
style={{ marginLeft: "15%" }}
defaultValue={subModule}
hintText="SubModule Title"
floatingLabelText="SubModule Title"
onChange={e =>
this.props.handleSubModuleChange(
e,
index2,
this.state.selectedIndex,
this.state.result,
values.subModuleTitle
)
}
/>
<input
type="button"
value="remove"
onClick={() =>
this.props.removeSubModule(index2, this.state.result)
}
/>
<br />
<br />
</div>
);
}
);
the selectIndex is the index of the json object which contains the title that i selected from the dropdown list
so when i select a first item from the select the loop works and when i try to change to a second item from the select it won't work it tells me cannot read SubModules of undefined because in my console it prints undefined then it prints the new state so of course it won't work because he sees undefined at first didn't know how to solve it
Array#splice mutates the array and since you're applying it to your state, you mutate it directly. You need to make a copy of state first, apply the changes to the copy and then set it on the state:
Affect_Module_Submodule = (currentModuleTitle, index) => {
let copyArray = this.state.Modules_SubModules_Array.concat();
if (copyArray.findIndex(item => item.Module == currentModuleTitle) < 0) {
copyArray.splice(index, 1, {
Module: currentModuleTitle,
SubModules: []
});
this.setState({
Modules_SubModules_Array: copyArray
});
}
};
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});
The code below is only working when I remove the componentWillMount that uses localStorage. With usage localStorage it gives a mistake
this.state.interests.map is not a function
I tried to move usage of localStorage out of component but it won't help. I suppose that using local storage somehow changes this.state.interests that they stop being an array.
let interests = ["Музыка", "Компьютеры", "Радио"]
let ListOfInterest = React.createClass({
getInitialState: function() {
return {value: '', interests: interests};
},
componentWillMount() {
let local = localStorage.getItem('interests')
if (local) {
this.setState({interests: local});
} else {
localStorage.setItem('interests', this.state.interests)}
},
deleteInterest(key) {
delete interests[key]
this.setState(this.state) // without this line the page will not re-render
},
addInterest() {
interests.unshift(this.state.value)
this.setState({value: ''})
},
handleChange(event) {
this.setState({value: event.target.value})
},
render() {
return <div className="interests">
<b>Интересы</b>
<br/>
{this.state.interests.map((int, index) => {
return <button onClick={() => {
this.deleteInterest(index)
}} key={index} className="btn-interest">{int}</button>
})}
<input type='text' placeholder="Add" value={this.state.value} onChange={(e) => this.handleChange(e)}/>
<button onClick={() => {
this.addInterest()
}} className="add">Add interest</button>
</div>
}
})
<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>
You have several issues in your example
in localStorage.setItem second argument have to be a String, you can not store Array(when you do it, in storage will be string separated by coma because called method toString - [1, 2, 3].toString() ), you need stringify array before set to Storage
keyValue A DOMString containing the value you want to give the
key you are creating/updating.
localStorage.setItem(
'interests', JSON.stringify(this.state.interests)
)
and parse when get value
let local = JSON.parse(localStorage.getItem('interests'));
this.setState(this.state) this is not good way to update state, you need update state like so
deleteInterest(key) {
this.setState({
interests: this.state.interests.filter((el, i) => i !== key)
})
},
addInterest() {
this.setState({
value: '',
interests: this.state.interests.concat(this.state.value)
});
},
Example