My goal is to map over an array and replace objects that match a condition. Much like this working example:
const rows = [{
"name": "Dad",
"car": "Sedan"
}, {
"name": "Mom",
"car": "Sedan"
}]
const newCar = {
"name": "Dad",
"car": "Minivan"
}
const newArray = rows.map((r) => (r.name === newCar.name ? newCar : r))
console.log(newArray)
In my use case, component state contains the array, which is populated from an API.
const [rows, setRows] = useState([])
Later, a change is required to one object in the array. The data variable contains the modified object to be merged into the array. I map over the array looking for matches on the _id field. When the _id matches, I return the object from data (the value to be merged into the array). When there is not a match, I return the object as it originally existed in state.
setRows((rows) => [
...rows,
rows.map((r) => (r._id === data._id ? data : r)),
])
The desired outcome is an array of the same size as the original. This new array should contain one modified object in addition to all original array values.
The actual results from the code above are that the modified data object is added rather than updated.
How can I change my code to replace the modified array element instead?
The useState() functional updates form calls a function and passes it the previous state. The Array.map() function returns a new arrays with the updated values. This means that you only need to map the previous state to get a new state:
setRows(rows => rows.map((r) => (r._id === data._id ? data : r)))
Related
I'm making a menu app where people can filter food and drinks. It's divided in categories, and categories contain items. One of the properties is 'alcohol' -> does the drink contain alcohol?
I have a state-object with 2 arrays: an 'original' array (containig all items) and a 'filter' array (where the filtering happens).
Every time a filter is changed, I want to copy the original array and start filtering all over again. But it seems that after filtering one time, the original array is changed, and it's weird because I clone the array.
My code:
const [arrays, setArrays] = useState({
original: [{ catName: 'drinks', items: [{},{},...]}],
filter: [] // same as original in the beginning
});
useEffect(() => {
const copy = [...arrays.original];
copy.forEach((cat, idx) => {
if (!filter.alcohol) {
copy[idx].items = cat.items.filter(e => e.properties.alcohol === false);
}
});
setArrays(p => ({
...p,
filter: copy
});
}, [filter]);
I never 'touch' the original array, but after a filter changes, not only the 'filter'-array is changed (like expected), but also the original array.
Edit: expected behaviour:
when I filter on alcohol, I'm expecting that all drinks containing alcohol are removed from the filter-array. This works, but when I remove the filter, all the alcohol-drinks should be back in the filter-array. This doesn't work because they are also deleted from the original array.
Sounds like you may just be returning a shallow copy of the original array. Have you tried Lodash's clonedeep?
I'm making a React-Native application. Thanks to everyone's help I could somehow make that work except for toggling YES and NO. Once the user clicks on a button I just want to check if that clicked item data already exists in the state, if so I want to update it. If it does not exist then it should be added to the state as a new Item.
I already coded the above logic, my code is working, but not returning correct output, elements are not adding or updating to the state array properly. How do I fix this code?
I want to generate output like this
[{
chelistid:231,
validiary : "YES",
remark : "Hello"
},{
chelistid:232,
validiary : "NO",
remark : "asddddd"
}]
My code
const [reqData, setReqData] = useState([]);
//Modify yes clicked
const yesClicked = (element) => {
let req = {
"chelistid": element.chelistid,
"validiary": "Yes",
"remark": element.remark
}
createCheckList(req);
}
//Modify no clicked
const noClicked = (element) => {
let req = {
"chelistid": element.chelistid,
"validiary": "No",
"remark": element.remark
}
createCheckList(req);
}
const createCheckList = (data) => {
const index = reqData.findIndex(x => x.chelistid === data.chelistid)
var modifiedArray = reqData
if (index !== -1) {
//Remove the element from the array
modifiedArray.splice(index, 1);
}
setReqData([modifiedArray, data]);
}
The problem is it seems like you are not spreading the array to append the data element. What you are doing by [modifiedArray, data] you are creating an array that contains an array and data something like [[modified array content here], data]. But actually, you want to append to modified array instead. For that, you need to expand the modified array by using ... which is called spread syntax. (Learn here) So, your code would look like:
setReqData([...modifiedArray, data]);
I have the state values as
this.state = {
emp: [
{id: "1", name: "A"}
{id: "2", name: "B"}
{id: "3", name: "B"}
]
}
How can I add an array like var arr = {id:"4", name:"D"} to the state emp without removing the current values of array. I just want to append the new array of values to the current state array. Can anyone help?
In modern JavaScript you can use the spread operator:
Add a single item
addItem = item => {
this.setState({
emp: [
...this.state.emp,
item
]
})
}
Add multiple items:
addItems = items => {
this.setState({
emp: [
...this.state.emp,
...items
]
})
}
The spread operator places all the elements in this.state.emp in a new array instance and item gets appended as the last element.
You should not mutate a component's state with other means than setState as your rendered data will get out of sync.
just use concat
this.setState({ emp: this.state.emp.concat('new value') })
The reasons why concat is better than push, unshift are
Array.push
Array.prototype.push allows us to push elements to the end of an
array. This method does not return a new copy, rather mutates the
original array by adding a new element and returns the new length
property of the object upon which the method was called.
Array.unshift
To add elements to the very beginning of an array. Just as push, unshift does not return a new copy of the modified array, rather the new length of the array
Both the ways changes the mutation state of an array. A mutation term is meant to be unchanged because it is our original source.
array.concat
The concat() method is used to merge two or more arrays. This method does not change the existing arrays, but instead returns a new array.
However, You Object.assign() too, that creates a deep copy of object assigned to it.
let emp = Object.assign([],this.state.emp); //type of an array
result
You need to update if using functional setState(since you are updating state based on prevState) and spread syntax like
this.setState(prevState => ({
emp: [
...prevState.emp,
{id:"4",name:"c"}
]
}))
If using a functional component, voila a simple example:
const [hiddenDivs, setHiddenDivs] = useState([1, 2, 3, 4]);
const handleCatDivTitleClick = (divNum: number) => {
if (hiddenDivs.includes(divNum)) {
setHiddenDivs(hiddenDivs.filter((d) => d !== divNum)); //REMOVE FROM ARRAY
} else {
setHiddenDivs([...hiddenDivs, divNum]); //ADD TO ARRAY
}
};
<div class={`${style.catDiv} ${hiddenDivs.includes(1) ? style.catDivHide : ''}`}>
<div class={style.catTitle} onClick={() => handleCatDivTitleClick(1)}>
Imagine a list of categories like this. All begin "hidden" (shrunk-up).
</div>
</div>
<div class={`${style.catDiv} ${hiddenDivs.includes(2) ? style.catDivHide : ''}`}>
<div class={style.catTitle} onClick={() => handleCatDivTitleClick(2)}>
You want to shrink/expand category based on clicking title.
</div>
</div>
<div class={`${style.catDiv} ${hiddenDivs.includes(3) ? style.catDivHide : ''}`}>
<div class={style.catTitle} onClick={() => handleCatDivTitleClick(3)}>
Basically, a home-rolled accordian-type display.
</div>
</div>
I'm trying to render some rows from firebase database, I'm getting this error:
TaskQueue: Error with task : Invariant Violation: Tried to get frame
for out of range index NaN
const { currentUser } = firebase.auth();
var userfavorites = firebase.database().ref(`/users/${currentUser.uid}/favorites/`);
userfavorites.once('value').then(snapshot => {
this.setState({ userfav: snapshot.val() })
})
...
<FlatList
data={this.state.userfav}
renderItem={({ item }) => (
<Text>{item.favdata}</Text>
)}
/>
I came across this error, I had a PHP back-end and trying to get json_encoded data into a FlatList.
Problem: The REST endpoint was returning an object eg
{"Total_Excavator":2,"Active_Excavator":2,"Total_load":6804}
Solution: Fixed it to return an array rather eg
[{"Total_Excavator":2,"Active_Excavator":2,"Total_load":6804}]
Take note of the Square Brackets.
I used $data[] = json_encode($excavatorArray) instead of
$data = json_encode($excavatorArray)
. Hope it helps someone one day
I had the same issue, it seems this problem is the reason of the object names.
In the image below you can see that as soon as you fetch snapshot from Firebase endpoint it comes with the id which is not recognized by React Native. And react acts like it's empty obj.
All you have to do is map the items of the object after fetching it like example below,
const fbObject = snap.val();
const newArr = [];
Object.keys(fbObject).map( (key,index)=>{
console.log(key);
console.log("||");
console.log(index);
newArr.push(fbObject[key]);
});
Just a slight modification to the answer from #mert. JavaScript's map operator returns an array; so, there is no need to push elements onto newArr. Also, the new array's elements are going to be missing the unique Firebase id. So, it should be added into the array element.
const fbObject = snapshot.val();
const newArr = Object.keys(fbObject).map((key) => {
fbObject[key].id = key;
return fbObject[key];
});
You'll end up with a newArray like this:
[
{
"id": "12345",
"name": "Jane Doe",
"age": 18
},
{
"id": "23456",
"name": "John Smith",
"age": 27
}
]
I had a similar problem and saved the flatlist's data in the state. You have to make sure that the data in the state is a list instead of an object:
this.state = {
...,
data: [], // <-- wrap your flatlist's data in there
}
When making changes to your data/the state, always use the callback function to perform something afterwards to keep your UI in sync:
this.setState({..., data}, () => yourCallback())
In one component I can filter my array using the following:
// Array of product objects
const result = products.filter(p => p.name.includes('val'));
and value of products remains same as the first value but filtered value stores in result.
But in the following code, filter() filters array of strings itself:
// Array of strings
const result = strs.filter(s => s.includes('val'));
The question is how can I filter strings and return result without modifying the strs itself?
Note: I tried with array.filter(function() { return res; }); but didn't make any change.
It returns the filtered ones and don't change the actual array. You are doing something wrong
const strs = ['valval', 'bal', 'gal', 'dalval'];
const result = strs.filter(s => s.includes('val'));
console.log(strs);
console.log(result);
First thing we need to know is, if we filter our list we loose our original data
products: any[] = [
{
"productId": 1,
"productName": "foo-bar",
"price": 32.99
}
]
and can't get it back without re-getting the data from it's source so we have to make another list to store the filtered list.
filteredProduce: any[];
Next if you are working to show a list of filtered product on a grid or something like this we need a way to know when the user changes the filter criteria. we could use event binding and watch for key presses or value changes, but an easier way is to change our _listFilter property into a getter and setter, like this
get listFilter: string {
return this._listFilter;
}
set listFilter(value:string) {
this._listFilter= value;
}
next we want to set our filteredProducts array to the filtered list of products like this
set listFilter(value:string) {
this._listFilter= value;
this.filteredProducts = this._listFilter? this.performFilter(this._listFilter) : this.products;
}
in preceding code we are using js conditional operator to handle the posibility that _listFilterstring is empty, null or undefined.
Next we use this.performFilter(this._listFilter) to filter our list.
performFilter(filterBy: string): any[] {
filterBy = filterBy.toLocaleLowerCase();
return this.products.filter((product: any) =>
product.productName.toLocaleLowerCase().indexOf(filterBy) !== -1);
}
Finally we should assign the main list of products to the filteredProducts and _listFilter to what we want.
constructor() {
this.filteredProducts = this.products;
this._listFilter= 'foo-bar';
}
last step is to change our template to bind to our filteredProducts property.