Update an object in a array - reactjs - reactjs

I'm calling an api request to update (edit) values in my array of objects. But the issue is i can only see the updated value after page refresh. To see the updated (edited) values instantly i'm trying to insert the updated values to my object locally (Only if the request is executed successfully). My approach is as follows
Action
.then(res => {
if (res.data.success === true) {
const updatedBillingPlan = {
"billingId":data.billingId,
"name": data.name,
"rateKWh": data.rateKWh,
"rateMin": data.rateMin,
"rateTransaction": data.rateTransaction
}
dispatch(updateBillingPlansSuccess(updatedBillingPlan));
} else {
alert("error");
}
})
Reducer
case UPDATE_BILLING_PLANS_SUCCESS:
let updatedBillingArray = (Here i need to include action.data to billingData )
return update(state, {
billingData: updatedBillingArray,
})
I'm trying to use immutability-helper library.
How would i go about this?
For example when i delete a record, to display the newest array with the deleted record , i'm doing something like this.
case DELETE_MODAL_SUCCESS:
let updatedBillingArray = state.billingData.filter(property => property.billingId !== action.data);
return {
...state,
billingData: updatedBillingArray,
billingDeleteToast: true
}
I want to do the same with update as well.

assuming billingID is unique among the billing plans, you could use that to update the proper element in the array:
case UPDATE_BILLING_PLANS_SUCCESS:
let updatedBillingArray = state.billingData.map(bp => if (bp.billingId === action.data.billingId) return action.data else return bp);
return update(state, {
...state,
billingData: updatedBillingArray,
})

Related

How to prevent adding duplicate items to cart using react

I wants to prevent addition of duplicate items to cart. I have tried the code below but it's working only for single item, when there are multiple items in the cart the duplicate items are allowed to add in to the cart. Here is my code
addToCart = (id) => {
let item = this.getItem(id);
if ([...this.state.cart]) {
[...this.state.cart].map((i) => {
if (i.product_name == item.product_name) {
alert("Item is already in cart");
} else {
this.setState((this.state.cart = [...this.state.cart, item]));
}
});
} else {
this.setState((this.state.cart = [...this.state.cart, item]));
}
console.log(this.state.cart);
};
You need to use map only to check if the item already exists, and then either add it or alert that the item is repeated.
One way of doing it would be like this:
existing = [...this.state.cart].map((i) => {
if (i.product_name == item.product_name) {
return i;
}
});
if (existing) {
alert("Item is already in cart");
} else {
this.setState((this.state.cart = [...this.state.cart, item]));
}
Explanation
map function executes the code for each of the items in the collection, which means the moment it finds an item in the cart different from the item selected, it will add the item selected.
So let's say your cart has [apple, orange] and you want to add apple again. When the map code executes it first looks like this:
if ("apple" == "apple") {
alert("Item is already in cart");
} else {
this.setState((this.state.cart = [...this.state.cart, apple]));
}
It doesn't add the item because it already exists... but then it executes a second time, and it looks like this:
if ("orange" == "apple") {
alert("Item is already in cart");
} else {
this.setState((this.state.cart = [...this.state.cart, apple]));
}
It gets added because the second item is different.
What the new code does is that it returns a value only if the item exists and, after looping throuhght all the items in the cart, it checks that value and adds the item if it is nothing.
An item should be added to the cart, if the latter doesn't contain it already.
To check if an Array contains an object, that fulfills a certain condition use the some method, as said by #Isaac Batista.
On the other hand, when you want to update state, by using it's previous value, you should use the callback argument of setState.
See https://reactjs.org/docs/state-and-lifecycle.html#state-updates-may-be-asynchronous
this.setState((state)=>{
// if cart already contains item
if(state.cart.some(itm=>itm.product_name == item.product_name)) {
return {}; // do not update state
} else {
return {cart: state.cart.concat(item)}; // add item to cart
}
}
You can use filter method to check whether the item is already available or not. With this you can also avoid the nested if condition also.
addToCart = (id) => {
let item = this.getItem(id);
let checkCart = [...this.state.cart].filter((i) => {
return i.product_name == item.product_name;
});
if (checkCart.length !== 0) {
alert("Item is already in cart");
} else {
this.setState((this.state.cart = [...this.state.cart, item]));
}
console.log(this.state.cart);
};
Here is a functional exemple, but note some points:
You are mutating state, and you should not do it, as it is explained here. So, you should just call setState passing the new value via argument, like this: this.setState(newValue).
A map is used to create a new array, the correct function to find out if some element passes a rule is some. This will allow you to check if some product inside cart is the clicked product.
// quick example
[1,2,3,4].some(number => number === 2) // true
[1,2,3,4].some(number => number === 5) // false
Finally, i would do something like this
const { cart } = this.state;
const product = this.getItem(id);
// returns true if there is any product with the same id
const isProductInCart = cart.some((item) => item.id === product.id);
if (isProductInCart) {
alert("Product already in cart");
} else {
this.setState({
cart: [...cart, product]
});
}

How to delete/set "null" nested property of state array in ReactJs Redux?

For example I have a site (location, not website). Under that there are 4 more "levels" of specification. For example lvl 1 - 2 - 3 - 4 can be "USA - New York - Brooklyn - streetX" or "CompanyA - buildingA - floorC - roomNumber".
I have to be able to delete the "lowest" specification level. So if you want to delete the roomNumber, because you moved an item to another room, floor etc... Only "roomNumber" should be deleted but "CompanyA - buildingA - floorC" should still exist in the state.
My API will return the last "level" so the record (with ID) of whatever is associated with "roomNumber". So, I have the ID to delete.
This is how my state looks:
So for example, I want to remove "lvl4test" level. Back-end wise I just send the ID, it deletes the record and on page-refresh (forced F5) it will realize this level is gone. But now in my reducer I don't know how to do it.
Here's my reducer:
case DELETE_SITE: {
console.log("delete site reducer reached");
return {
...state,
// somehow makes all "siteRows" == "undefined"
siteRows: state.siteRows.map((s) => {
if (s.siteLevel2Id && s.siteLevel2Id == action.payload.id)
s.siteLevel2Id = null;
if (s.siteLevel3Id && s.siteLevel3Id == action.payload.id)
s.siteLevel3Id = null;
if (s.siteLevel4Id && s.siteLevel4Id == action.payload.id)
s.siteLevel4Id = null;
if (s.siteLevel5Id && s.siteLevel5Id == action.payload.id)
s.siteLevel5Id = null;
})
}
}
If I do this, it will make all "siteRows" as "undefined" when React's trying to refresh?
I don't know if I should use "filter" but don't know how to do so on nested objects/foreach objects and then filter on their properties etc...
Basically, all I want is "siteLevel4Name" and "siteLevel4Id" to be "null" as well and then React refreshing state and my webpage. Does anyone know how to do this?
EDIT: in "action.payload" I get the item I want to delete, so "action.payload.id" for deleting "lvl4test" will be "d117c1f6-..."
.map have to return value, not just modify it. You return nothing, hence the undefined. At the end of your mapping function you should return new s value.
function reducer(state, action) {
return {
...state,
siteRows: state.siteRows.map((s) => {
if (s.siteLevel2Id && s.siteLevel2Id == action.payload.id)
return { ...s, siteLevel2Id: null };
if (s.siteLevel3Id && s.siteLevel3Id == action.payload.id)
return { ...s, siteLevel3Id: null };
if (s.siteLevel4Id && s.siteLevel4Id == action.payload.id)
return { ...s, siteLevel4Id: null };
if (s.siteLevel5Id && s.siteLevel5Id == action.payload.id)
return { ...s, siteLevel5Id: null };
return s;
}),
};
}

How can I change a data parameter in Angular 2?

I have a problem changing a data parameter in my component file:
this.commandList.ListesCommandesATransmettre.forEach(data => {
this.catalogs.forEach(catalog => {
if (catalog.Libelle === data.Catalogue) {
if (catalog.selected === false) {
console.log(data.isSelected)
data.isSelected = false;
console.log(data.isSelected)
console.log(data);
}
}
})
});
This code displays:
true
false
But the value of isSelected in console.log(data) is still true. So, why is the result false?
Try this if it's a loop problem with the callback
for (let data of this.commandList.ListesCommandesATransmettre) {
for (let catalog of this.catalogs) {
if (catalog.Libelle === data.Catalogue) {
if (catalog.selected === false) {
console.log(data.isSelected)
data.isSelected = false;
console.log(data.isSelected)
console.log(data);
}
}
}
}
I cant understand logic of your code. What happen if this.catalogs has many catalogs that meet the condition? isSelected will be setted many times in false.
Maybe this works (if I understand your logic):
this.commandList.ListesCommandesATransmettre = this.commandList.ListesCommandesATransmettre.map(data => {
const mutateData = Object.assign({}, data);
// If condition math, then isSelected set in false. Else, isSelected keep the his value
mutateData.isSelected =
this.catalogs.some(catalog => catalog.Libelle === data.Catalogue && !catalog.selected) ? false : mutateData.isSelected;
return mutateData;
})
Try this code to test if it is updating correctly. the loop will tell which are true/false and you can see if that is the case in the updated data.
this.commandList.ListesCommandesATransmettre.forEach((data, index) => {
this.catalogs.forEach(catalog => {
if (catalog.Libelle === data.Catalogue) {
if (catalog.selected === false) {
data.isSelected = false;
}
console.log(index + 'is selected: ' + data.isSelected)
}
})
});
console.log(this.commandlist);
Something I have noticed is that web browsers will only keep the the 'most current' reference of an object in memory. So after a loop has ended, the 'most current' reference is the last one to fire. So you may be looking at your last object and not the one you are trying to test. If someone else knows more, or has a reference to what is actually happening, please edit this post.

Can I update various items in an Immutable List in a React/Redux App

On submitting a form with some updated values, I need to update the state to reflect these changes, but I am new to Immutable.js and am unsure how to do so.
Is it possible to pass a function as a 2nd argument to set or update to update values based on certain criteria.
I have a function which receives state and an array of objects called values. The data in values looks like this:
[
{
key: 'name',
value: 'fred'
},
{
key: 'height',
value: '201'
},
{
key: 'weight',
value: '78'
}
]
I need to map over this data, and the state list, and update the corresponding values in the state list with the values array.
How can I do this. I have put together a function which the Reducer calls to update the state with the new data, but unsure exactly how to get the end result
function updateValue(state, values = []) {
const items = state.get('items').map((i) => {
const key = i.get('key');
values.map(v => {
if (v.key === key) {
return state.update('value', v.value);
}
})
});
return state.update('items', /* Can I use a function here to replace the code above.. to update all of the items in the state List that correspond to the items in the measurements array (which have new values) */);
}
Thank you very much.
Update
Tried the following, but getting the error: Expected [K, V] tuple: i
function updateValue(state, values = []) {
const items = state.get('items').map((i) => {
const key = i.get('key');
values.map(v => {
if (v.key === key) {
return state.update('value', v.value);
}
})
});
return state.update('items', items);
}
More details on the error from Immutable:
function validateEntry(entry) {
if (entry !== Object(entry)) {
throw new TypeError('Expected [K, V] tuple: ' + entry);
}
}
You can use 'merge' to return new object:
return state.merge({
items: values,
});

React: Syntax for calling setState in Switch Return

Having a hard time figuring out what I'm doing wrong here.. and I'm sure it's something simple I'm missing. Just trying to increment counters on state whenever the data passed into this switch call matches each category, but for some reason the counter doesn't increment...
countCategories(cart) {
cart.map(function(item){
switch (item.type) {
case "beverage": return () => { this.setState({
countBeverage: this.state.countBeverage + 1
}); }
case "fruit": return () => { this.setState({
countFruit: this.state.countFruit + 1
}); }
case "vegetable": return () => { this.setState({
countVegetable: this.state.countVegetable + 1
}); }
case "snack": return () => { this.setState({
countSnack: this.state.countSnack + 1
}); }
default: return console.log("unknown category");
};
}); }
I also tried it this way, but I don't think I have a reference to 'this' when I call it this way:
countCategories(cart) {
cart.map(function(item){
switch (item.type) {
case "beverage": return this.setState({
countBeverage: this.state.countBeverage + 1
})
case "fruit": return this.setState({
countFruit: this.state.countFruit + 1
})
case "vegetable": return this.setState({
countVegetable: this.state.countVegetable + 1
})
case "snack": return this.setState({
countSnack: this.state.countSnack + 1
});
default: return console.log("unknown category");
};
}); }
Thank you so much for your help!
Just do this dude:
let food = [{type: "snack"},{type: "snack"},{type: "snack"},{type: "snack"},
{type: "veggi"},{type: "veggi"},{type: "veggi"},{type: "veggi"}]
let foodCount = {
veggiCount: this.state.veggiCount || 0,
snackCount: this.state.snackCount || 0,
beefCount: this.state.beefCount || 0,
fruitCount: this.state.fruitCount || 0
};
food.map(item => foodCount[item + "Count"]++ )
this.setState(foodCount)
The important thing here, is to setState 1. once, 2. when the calculation has completed. Avoid setting the state in loops or iterations like for(...) setState()
Assuming you're invoking countCategories bound to the component (the value of this is the component), in your first code, which should work, you could change the mapping function to an arrow function, so it keeps the this value of the countCategories function. Another weird thing I've noticed is that you're creating an array of functions by returning functions that should change the state, instead of actually changing the state:
countCategories(cart) {
// Notice the change in the next line
cart.map(item => {
switch (item.type) {
case "beverage":
// Set the state instead of returning a function that sets the state
this.setState({
countBeverage: this.state.countBeverage + 1
});
break;
case "fruit":
this.setState({
countFruit: this.state.countFruit + 1
});
break;
case "vegetable":
this.setState({
countVegetable: this.state.countVegetable + 1
});
break;
case "snack":
this.setState({
countSnack: this.state.countSnack + 1
});
break;
default:
console.log("unknown category");
break;
};
});
}
An important consideration here is that setState is asynchronous so you can't read the value in the same execution cycle and increment it. Instead, I'd suggest creating a set of changes and then apply them in a single setState.
Below I've used map to iterate over the cart and and increment the state values stored in a cloned copy of state (because this.state should be considered immutable). Then once complete, the state is updated.
let newState = Object.assign({}, this.state);
cart.map((item) => {
// adapt type to state string -> snack to countSnack
const type = item.type.replace(/^(.)/, (s) => 'count' + s.toUpperCase());
newState[type] = (newState[type] || 0) + 1;
});
this.setState(newState);
See notes within Component API documentation for details

Resources