How to prevent adding duplicate items to cart using react - reactjs

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]
});
}

Related

Arraylist doesn't get refilled and/or filtered

I have a list in angular, an array. OnInit it gets filled from the right corresponding database items. I created a form above it. When you enter something in the form, it acts like a filter. This works, the first time. When you erase something from the form and enter something else, the list should be refreshed and afterwards filtered based on the new input. This doesn't happen. I put the formula that happens on onInit in my function to refill the list.
Below you can find my function (I left the console logs in) and a screenshot of the problem. First I look for a user (joeri.boons#hi10.be) which returns three results. Than I erase the user and look based on a month 7. The screen returns a new unfilterd list while in the console it still holds the list of 3 x user joeri.boons#hi10.be. So there is an inconsistency to. If you look at screen result you would think of a filter problem, the console points at a refreshproblem.
if more code is required let me know.
updateWithFilter(): void {
console.log("function update filter reached")
console.log(this.listadapted);
if(this.listadapted == true){
// this.timesheetsHandled = {} as TimeSheet[];
this.getHandledSheet();
console.log("getHandledSheet executed")
}
if(this.filterUsername.trim() && !this.filterYear && !this.filterMonth){
console.log("option 1 reached")
console.log(this.filterUsername.trim());
console.log(this.filterYear);
console.log(this.filterMonth);
this.timesheetsHandled = this.timesheetsHandled.filter(sheet => sheet.username == this.filterUsername);
this.listadapted = true;
} else if(!this.filterUsername.trim() && !this.filterYear && this.filterMonth){
console.log("option 2 reached");
console.log(this.filterUsername.trim());
console.log(this.filterYear);
console.log(this.filterMonth);
console.log("before filter");
this.timesheetsHandled.forEach(sheet => console.log(sheet.username));
this.timesheetsHandled = this.timesheetsHandled.filter(sheet => sheet.month == this.filterMonth);
console.log("after filter");
this.timesheetsHandled.forEach(sheet => console.log(sheet.username));
// console.log(this.timesheetsHandled.filter(sheet => sheet.month == this.filterMonth));
this.listadapted = true;
} else if .. more options
}
ngOnInit(): void {
this.getHandledSheet();
}
getHandledSheet(): void {
this.timesheetService.getAllTimesheets().subscribe({next: (response: TimeSheet[]) => {this.timesheetsHandled = response.filter(sheet => sheet.status == 'HANDLED') }}) ;
}
My guess would be that this is caused by loading data in ngOnInit. As the documentation (https://angular.io/api/core/OnInit) states : [...] It is invoked only once when the directive is instantiated.
I suspect that you create one instance and re-use it and the ngOnInit method does not get called again.
UPDATE:
The issue is that the call to this.getHandledSheet(); does a call to .. .subscribe({next: .. which is delayed and the rest of the function is executed first.
So the actual code after next: is only executed after the timeSheetService is done loading the data.
So either you apply the filter in the
{next: (response: TimeSheet[]) => {this.timesheetsHandled = response.filter(sheet => sheet.status == 'HANDLED') }}
block after filtering for 'HANDLED' or you'll try to await in the update function.
Create two variables, one that will always remain unfiltered, then another that will be filtered.
The problem will be that the original list is filtered, hence you are losing the original data after filtering!
timesheetHandled: TimeSheet[];
timesheetHandledOriginal: TimeSheet[];
updateWithFilter(): void {
console.log('function update filter reached');
console.log(this.listadapted);
if (this.listadapted == true) {
// this.timesheetsHandled = {} as TimeSheet[];
this.getHandledSheet();
console.log('getHandledSheet executed');
}
if (this.filterUsername.trim() && !this.filterYear && !this.filterMonth) {
console.log('option 1 reached');
console.log(this.filterUsername.trim());
console.log(this.filterYear);
console.log(this.filterMonth);
this.timesheetsHandled = this.timesheetHandledOriginal.filter(
sheet => sheet.username == this.filterUsername
);
this.listadapted = true;
} else if (!this.filterUsername.trim() && !this.filterYear && this.filterMonth) {
console.log('option 2 reached');
console.log(this.filterUsername.trim());
console.log(this.filterYear);
console.log(this.filterMonth);
console.log('before filter');
this.timesheetsHandled.forEach(sheet => console.log(sheet.username));
this.timesheetsHandled = this.timesheetHandledOriginal.filter(
sheet => sheet.month == this.filterMonth
);
console.log('after filter');
this.timesheetsHandled.forEach(sheet => console.log(sheet.username));
// console.log(this.timesheetsHandled.filter(sheet => sheet.month == this.filterMonth));
this.listadapted = true;
}
// else if .. more options
}
ngOnInit(): void {
this.getHandledSheet();
}
getHandledSheet(): void {
this.timesheetService.getAllTimesheets().subscribe({
next: (response: TimeSheet[]) => {
this.timesheetsHandled = response.filter(sheet => sheet.status == 'HANDLED');
this.timesheetHandledOriginal = JSON.parse(JSON.stringify(this.timesheetsHandled));
},
});
}

Query Parameter Values Not Updating When Set from Google Map Component

I have a map component that contains some clickable overlays on the map. Users can click and unclick the overlays on the map to select them and when they do so the app loads some data based on the overlays that are currently selected.
The current structure is as follows:
User clicks the map which executes a function passed to the map as a prop, which takes the current value of the neighborhoods and either adds or removes them from the query string.
The function executes a history.push()
I use a useEffect checking the value of the query param neighborhood and send a request to the backend to fetch the listings if the values have changed.
My issue is that when the user clicks on the map, the function executes but the value pushed to the params is never updated, causing the logic to fail the next time the user clicks on the map.
Relevant Snippets of Code are as follows:
history/param variables:
const { region, state, neighborhood, transactiontype } = useParams();
const location = useLocation();
const { pathname, search } = location;
Function that is passed down to the child map component:
const updateChildComponentHandler = (dataFromChild, addorRemove) => {
SetLastSetter("map");
SetNeighborhoodTypeSelected("custom");
// if a new neighborhood is added, just taking the existing string and adding &`neighborhood`
if (addorRemove === "add") {
let newGroup = ""
if (neighborhood !== "any") {
newGroup = `${neighborhood}&${dataFromChild}`;
SetMapChangeType("add");
}
// if no neighborhood was initially selected, just replacing the "any" with the proper neighborhood
if (neighborhood === "any") {
SetMapChangeType("add");
newGroup = `${dataFromChild}`
}
// pushing the new parameter string
const newPath = pathname.replace(neighborhood, newGroup);
history.push(`${newPath}${search}`);
}
// same concept as above, just removing the neighborhood from the string if it is removed from the map
if (addorRemove === "remove") {
let newGroup;
if (neighborhood !== dataFromChild) {
newGroup = neighborhood.replace(`&${dataFromChild}`, "")
SetMapChangeType("delete");
}
if (neighborhood === dataFromChild) {
newGroup = "any";
SetMapChangeType("add");
}
if (neighborhood.split("&")[0] === dataFromChild && neighborhood !== dataFromChild) {
newGroup = neighborhood.replace(`${dataFromChild}&`, "")
SetMapChangeType("delete");
}
const newPath = pathname.replace(neighborhood, newGroup);
const newerPath = `${newPath}${search}`;
history.push(newerPath);
deleteListings(dataFromChild);
}
SetUpdateMap(true);
}
UseEffect Logic:
useEffect(() => {
const func = async () => {
let neighborhoodParams;
if (neighborhood !== "any") {
neighborhoodParams = neighborhood.replace("%20", " ").split("&")
}
if (neighborhood === "any") {
neighborhoodParams = [];
neighborhoodParams[0] = "any";
}
const neighborhoods = neighborhood.split("%20").join("_");
SetNeighborhoodParams(neighborhoodParams);
SetNeighborhoodParamString(neighborhoods);
if (mapChangeType === "add") {
if (neighborhoodParams.length > 0) {
if (!requestMulti) {
await fetchProperties(transactiontype, neighborhoodParams[neighborhoodParams.length - 1].split(" ").join("_"), filteredRegion, state, filters, "single")
}
if (requestMulti) {
await fetchProperties(transactiontype, neighborhoods, filteredRegion, state, filters, "multi")
SetRequestMulti(false);
}
}
}
}
func();
}, [neighborhood]);
The issue I am experiencing is that when the map initially loads, the neighborhood is set to "any". The first time a user clicks an overlay for a neighborhood, the correct data is sent from the map and the map/data requests update and the URL parameter up top shows the new neighborhood. However, the second time a user clicks, the value of { neighborhood } is not updated and is still set to "any", so the function just replaces the value of { neighborhood } rather than adding it on as per above. I previously coded this with class components and am trying to convert it to hooks, but it seems like there is some dissonance that is causing the map component not to have access to the updated history variable. I am new to react/hooks, and appreciate if anyone could lend some advice.
Thanks!

Update Set after delete using a checkbox to delete Set Item

I am using a checkbox to select items.
When the checkbox is checked, item is added to a Set stored in state.
When checkbox is unchecked, item is deleted from the Set stored in state
Upon verifying if item was removed from Set, the function
set.has(setItem)
returns false.
However when checking the size of the Set, it is as if I did not remove anything.
I'm probably not updating the set to account for the deleted item.
Can anyone help?
my handleChange function below...
const handleChange = (e) => {
if (e.target.checked) {
var checkedItemId = parseInt(e.target.value)
setCheckedItems(new Set(checkedItems.add(checkedItemId)))
}else if(!e.target.checked){
checkedItems.delete(checkedItemId)
setCheckedItems(new Set(checkedItems))
}
}
const handleChange = (e) => {
const checkedItemId = parseInt(e.target.value);
if (e.target.checked) {
setCheckedItems(prevState => {
let newSetCheckedItems = new Set(...prevState.checkedItems);
return newSetCheckedItems.add(checkedItemId);
})
} else if (!e.target.checked) {
setCheckedItems(prevState => {
let newSetCheckedItems = new Set(...prevState.checkedItems);
return newSetCheckedItems.detele(checkedItemId);
});
}
}
The solution was to use a deep copy of the set.
Do the adding/removing operations on that deep copy.
Then update the set with the deep copy.
//i don't know about the react but i can give you solution in the jquery.
var new_arr=[];
$(document.body).on('click','.class_name',function(){
var is_checked = $(this).is(':checked');
var value_check_box = $(this).val();
if(is_checked==true){
// push the value of the check box in the array variable
new_arr.push(value_check_box);
}else{
var removeItem = value_check_box // it is the value when the user uncheck the uncheckbox;
new_arr = jQuery.grep(new_arr, function(value) {
return value != removeItem;
});
};
})

FindOne not finding item in array properly discordjs

I am trying to find an item in a user's array of items with his id on discord. I have this
const data = inv.findOne({User: message.author.id})
if(!data) {
return message.reply("You own no items. Buy something to trade")
}
if(item === 'hat') {
const itemfound = data.find({User: message.author.id, Items: '🎩Hat'})
if(itemfound) {
return message.reply("FOund!")
}
}
But it keeps returning found when I don't have that item in my array of items
Assuming inv is a Model, inv.findOne(...) returns a Query, and data.findOne(...) also returns a Query. The Query is getting converted to a truthy value in if(itemfound) and falsy in if(!data). Adding await and exec()(optional) statements should fix the issue. Also, data.find().exec() returns an array, which is truthy even if it is empty.
const data = inv.findOne({User: message.author.id})
if(!await data.exec()) {
return message.reply("You own no items. Buy something to trade")
}
if(item === 'hat') {
const itemfound = await data.find({User: message.author.id, Items: '🎩Hat'}).exec()
if(!itemfound.length) {
return message.reply("FOund!")
}
}

Best way to remove a specific item in AsyncStorage

I'm storing an array of objects in AsyncStorage and would like to know the best way to remove a specific object. Right now I´m passing an id to my function and then I loop through the array to match the id and remove the object and then the array in AsyncStorage is updated. This seems to work ok, but I wonder if this is optimal or if there is a better way to do this?
My function right now:
export function removeData(id) {
AsyncStorage.getItem('#books')
.then((books) => {
const updatedBooks = (JSON.parse(books))
for (let i = 0; i < updatedBooks.length; i++) {
if(updatedBooks[i].id == id) {
updatedBooks.splice(i, 1);
}
}
AsyncStorage.setItem('#books', JSON.stringify(updatedBooks));
})
}
My function for adding data to AsyncStorage:
export function addData(book) {
AsyncStorage.getItem('#books')
.then((books) => {
const b = books ? JSON.parse(books) : [];
b.push(book);
AsyncStorage.setItem('#books', JSON.stringify(b));
});
}
Button to add data with sample data to show structure:
<Button
title = "Add book"
onPress={() => addData({
id: 1,
title: 'Harry Potter',
author:'J.K. Rowling',
thumbnail:'https://covers.openlibrary.org/w/id/7984916-M.jpg',
})
To remove single item
AsyncStorage.removeItem('key', (err) => {
// key 'key' will be removed, if they existed
// callback to do some action after removal of item
});
To remove multiple items
let keys = ['k1', 'k2'];
AsyncStorage.multiRemove(keys, (err) => {
// keys k1 & k2 removed, if they existed
// callback to do some action after removal of item
});
Reference:
RemoveItem method
MultiRemove method

Resources