I working on adding/removing items to a shopping cart in my react js project after I add items to the cart I add "-" and "+" buttons that on click should decrease/increase item quantity. I've managed to make the add-to-cart, increase work but I can't figure out how to delete the item from the cart when the quantity becomes 0. This is my code so far:
const [items, setItems] = useState([]);
const handleDecrease = (id) => {
setItems((prevState) =>
prevState.map(
(item) =>
item.id === id
? item.qty !== 1
? { ...item, qty: item.qty - 1 }
: item.id !== id
: item // !id
)
);
};
{items?.map((item) => {
return (
<div
key={item.id}
>
<div onClick={() => handleDecrease(item.id)}>-</div>
<div>{item.title}</div>
<div> ${item.price * item.qty}</div>
<div>{item.qty}</div>
</div>
);
})}
In my handleDecrease function, I check if the item quantity is !==1, then I decrease the quantity by 1; if the quantity is 1 and "-" is clicked again, I want to remove the item entirely from the items array, but my code only adds false to the items array. How can I remove the item?
There are multiple ways to solve this problem, but the simplest is using the helping array:
const handleDecrease = (id) => {
const newItems = [];
items.map((item) => {
if (item.id === id) {
item.qty !== 1 && newItems.push({ ...item, qty: item.qty - 1 });
} else {
newItems.push(item);
}
});
setItems(newItems);
};
So what you want then is for when the user hits decrease:
If the (current) items object property qty’s value is greater than one, decrease the (current) items object property qty’s value by one.
Else delete the item object all together Or better yet delete an item object who has an id property value that matches the (current) item objects property id’s value from an array of items.
In addition to using map you can use the filter method. That way you can simplify the map logic up to only decrease the quantity then run a filter that will look for any item with quantity at 0 and remove it from the array. You can chain these together like so.
const handleDecrease = (id) => {
setItems((prevState) =>
prevState.map(item => item.id === id ?
({...item, qty: item.qty - 1})
: item
).filter(item => {
return item.id === id ?
item.qty > 0
: true
}
)
)
;
};
const handleDecrease = (id) => {
setItems((prevState) => {
const updatedState = { ...prevState };
const indexOf = updatedState.findIndex((elm) => elm.id === id);
if (indexOf > -1) {
updatedState[indexOf].qty > 0
? (updatedState[indexOf].qty -= 1)
: updatedState.splice(indexOf, 1);
}
return updatedState;
});
};
Related
This is the section of code I am dealing with,
This is my hook
const [array, setArray] = useState(
JSON.parse(localStorage.getItem("notes")) ?? []
);
And this is the function,
const save = () => {
let info = one.current.value;
console.log(newIndex, " and ", info);
console.log("this si array element -> ", array[newIndex - 1]);
array[newIndex - 1] = info;
if (newIndex !== undefined) {
setArray((e) => {
return e.map((e1, i) => {
if (i + 1 === newIndex) {
return [...e ];
}
});
});
}
};
info has the correct input I want to update to and newIndex have the index of the element I wish to update.
Can you suggest to me what I need to change in this section, this is part of the above function,
setArray((e) => {
return e.map((e1, i) => {
if (i + 1 === newIndex) {
return [...e ];
}
});
To update your array you do not need to map anything. You only need to copy the array, modify the copied array at the right index and return the copied array. React will take care of the rest.
It's important, that you copy the array, else react won't notice the change. useState only remembers the array's address in memory.. Only through a new array with a new address will useState actually fire.
Here is my take on what I think your save() function should look like:
const save = () => {
let info = one.current.value;
if (newIndex !== undefined) {
console.log(newIndex, " and ", info);
console.log("this is array element -> ", array[newIndex - 1]);
setArray((e) => {
let temp = [...e];
temp[newIndex - 1] = info;
return temp;
});
}
};
Tried the code in codesandbox and I think this is what you want.
I need to find index of an item from a list of objects.
First I have to check if that item exist with status of WAITING.
If no item with that status exist, then find something else with any other status.
Is there any better solution for this?
x in this code coming from a map
MainArray.map((x) => {
let itemIndex = orders?.findIndex(item => item.status === 'WAITING' && item.slot=== (x));
if (itemIndex === -1) {
itemIndex = orders && orders.findIndex(item => item.slot === (x));
}
return itemIndex;
}
There won't be a reasonble "single line solution" (those are over-rated in any case; hard to read, hard to debug); but you can avoid searching through the array twice by using a for loop:
const indexes = MainArray.map((x) => {
let bySlotIndex;
for (let index = 0, length = orders?.length; orders && index < length; ++index) {
const order = orders[index];
if (item.slot === x) {
bySlotIndex = bySlotIndex ?? index;
if (item.status === "WAITING") {
return index; // Found a waiting one, we're done
}
}
}
return bySlotIndex ?? -1;
});
Or if you really want to use findIndex, you can avoid some searching by finding the first one with a matching slot first:
const indexes = MainArray.map((x) => {
const bySlotIndex = orders?.findIndex(order => order.slot === x) ?? -1;
if (bySlotIndex === -1) {
return -1;
}
const waitingIndex = orders.findIndex(
order => order.status === 'WAITING' && order.slot === x,
bySlotIndex // Start at the first one with a matching slot
);
return waitingIndex === -1 ? bySlotIndex : waitingIndex;
});
Note that both of the above return -1 if orders is falsy, rather than undefined. Tweak if you really wanted undefined.
You can use findIndex to look for an item with status equal to 'WAITING'. in case, no item exists, use findIndex to return the first item with a status.
const arr = [{status: "WAITING", slot : 1}, {status: "NOTWAITING", slot: 2}];
const getIndex = (arr) => {
const idx = arr.findIndex(x => x.status === 'WAITING');
return idx !== -1 ? idx : arr.findIndex(x => x.status);
}
console.log(getIndex(arr));
I have multiple if statements which I have presented using ternary operator and then for each If a separate filter, This works perfectly fine in React, but the repetition of filter statement makes the code look untidy, id there a way to remove this repetition?
let ar = data;
id === 'A' ? ar = ar.filter(item => item > 4) : Id === 'B' ? ar = ar.filter(item => item > 4
&& item < 8) : Id === 'C' ? ar = ar.filter(item => item >8 && item < 15) : ar = data;
Let's run through some refactoring and see what we can improve. First I'll split it across multiple lines just so that I can see it easier:
let ar = data;
id === 'A' ?
ar = ar.filter(item => item > 4) :
id === 'B' ?
ar = ar.filter(item => item > 4 && item < 8) :
id === 'C' ?
ar = ar.filter(item => item > 8 && item < 15) :
//default
ar = data;
First thing I notice is that you are assigning multiple times, but using a ternary, which makes that unneeded. Let's fix that:
let ar = data
ar = (
id === 'A' ?
ar.filter(item => item > 4) :
id === 'B' ?
ar.filter(item => item > 4 && item < 8) :
id === 'C' ?
ar.filter(item => item > 8 && item < 15) :
//default
data
)
Next, ar.filter is getting called every time (except for the default case), and only the arguments are changing. The default case can be changed to be a filter that returns everything so that it is more consistent, and then we can extract the arguments to a variable (let's call it filter):
let ar = data
// we are just determining the argument here
let filter = (
id === 'A' ?
item => item > 4 :
id === 'B' ?
item => item > 4 && item < 8 :
id === 'C' ?
item => item > 8 && item < 15 :
// default is to return all items
item => true
)
// now we can use the argument in the function call
ar = ar.filter(filter)
That removes most of the duplication here, but we are comparing to id in the same way multiple times. Like Pavlos mentioned, if you want to pick an option based on a string id, basic objects can let you do that. The problem is we loose our default argument, but we can usually use the || ("or") operator to fix that:
let ar = data
// another benefit of doing it this way is that it is super easy to add
// new filters now, and you can even pass this in as a prop to your component.
let filters = {
'A' : item => item > 4,
'B' : item => item > 4 && item < 8,
'C' : item => item >8 && item < 15
}
let defaultFilter = item => true
// pick the right filter
let filter = filters[id] || defaultFilter
// now filter the collection with that filter
ar = ar.filter(filter)
With this, you've turned an imperative process of picking the right filter into a declarative process. The filters object now lists the filters without any logic. This is much more flexible and allows for more future refactoring in other parts of the codebase.
One other tip that might help would be rearrange the argument so that item is in the middle, and the numbers go from smallest to largest:
item => 4 < item && item < 8
(See this answer for more)
You can create a function that will return the filter function, like:
function makeFilterFn(id){
switch (id){
case 'A':
return item => item > 4
case 'B':
return item => item > 4 && item < 8
case 'C':
return item => item > 8 && item < 15
default:
return null
}
}
const filterFn = makeFilterFn(id)
const result = filterFn ? ar.filter(filterFn) : ar
const ar = [1,2,3,4,5,6,7,8,9,10];
const id = "A";
const filtered = {
"A": ar.filter(item => item > 4),
"B": ar.filter(item => item > 4 && item < 8),
"C": ar.filter(item => item >8 && item < 15)
}[id]
console.log(filtered)
I assigned a data that is returned from an API to a variable named todayData. There's a child object called meals which in it has a property name.
What I want to achieve is to count the number of occurrences in the name property of the meals object.
For example, the meal Rice can have multiple occurrences in the data.
DATA
[{"id":5,"referenceId":1189,"firstName":"Dan","lastName":"Daniels","orders":[{"id":109,"meals":[{"id":47,"name":"Fried Rice","description":"This is a very sweet meal","image":"","mealType":"LUNCH","unitPrice":-20,"status":"ENABLED"}],"serveDate":"2019-07-11 00:00:00"}]}]
JS
let occurences = this.todayData.reduce(function (r, row) {
r[row.orders.meals.name] = ++r[row.orders.meals.name] || 1;
return r;
}, {});
let result = Object.keys(occurences).map(function (key) {
return { meals: key, count: occurences[key] };
});
console.log(result);
SOLUTION:
r[row.orders[0].meals[0].name] = ++r[row.orders[0].meals[0].name] || 1;
Since some properties are of Array type, index should be set.
EDIT 1:
Solution for data with multiple orders along with multiple meals. (Thanks to Bill Cheng for making me to consider general approach.)
let mealOccureneceCount = {};
let occurences = this.todayData.forEach(user => {
user.orders.forEach(order => {
order.meals.forEach(meal => {
mealOccureneceCount[meal.name] = (mealOccureneceCount[meal.name] || 0) + 1;
});
});
});
console.log(mealOccureneceCount);
const occurences = this.todayData.reduce((r1, c1) => c1.orders.reduce((r2,c2) => c2.meals.reduce((r3,c3) => { r3[c3.name]= (r3[c3.name] || 0) + 1; return r3;}, r2), r1),{});
const result = Object.entries(occurences).map(([key, value]) => ({ meals: key, count: value }));
console.log(result);
I have just learned about MapReduce, so I wondered if there are any advantages in writing
const initialValue = 0;
if (this.items) {
return this.items.filter(function (item) {
return item && item.quantity && item.price;
}).reduce(function(previousValue, currentValue) {
return previousValue + currentValue.quantity * currentValue.price ;
}, initialValue);
} else {
return initialValue;
}
instead of just
let total = 0;
if (this.items) {
this.items.forEach(function(item) {
if (item && item.quantity && item.price) {
total += item.quantity * item.price;
}
});
}
return total;
For future readers, there are a few more idiomatic ways to write the reduction in a functional way.
These are generally used because they convey intent a bit more cleanly (and don't add a variable to the scope).
Note: I am assuming this.items has type
({ quantity: number; price: number } | undefined)[] | undefined
but each of the examples is tolerant to even more invalid data than the two in the question.
Filtering and mapping before reducing
Default value at the end
return this.items
?.filter(item => item?.quantity && item.price)
.map(item => item.quantity * item.price)
.reduce((a, b) => a + b, 0) ?? 0
Default array at the start
return (this.items ?? [])
.filter(item => item?.quantity && item.price)
.map(item => item.quantity * item.price)
.reduce((a, b) => a + b, 0)
Handling the filter within the map
I would not recommend these just because the previous two convey intention more clearly.
Default value at the end
return this.items
?.map(item => (item?.quantity ?? 0) * (item?.price ?? 0))
.reduce((a, b) => a + b, 0) ?? 0
Default array at the start
return (this.items ?? [])
.map(item => (item?.quantity ?? 0) * (item?.price ?? 0))
.reduce((a, b) => a + b, 0)
Destructuring
Each of the previous examples can be done with destructuring instead.
I am including one example.
return (this.items ?? [])
.filter(item => item) // Ensure item exists; sufficient for the cases we need to worry about
.map(({ price = 0, quantity = 0 }) => quantity * price)
.reduce((a, b) => a + b, 0)
Without a map
We can now do the reduction without a map.
This can also be done without destructuring, but that is seemingly (to me) inelegant.
return (this.items ?? [])
.filter(item => item)
.reduce((sum, { price = 0, quantity = 0 }) => sum + quantity * price, 0)
Of course, you can change the filter condition, which takes us back to roughly the first example in the question:
return (this.items ?? [])
.filter(item => item?.price && item.quantity)
.reduce((sum, { price, quantity }) => sum + quantity * price, 0)
Original forEach loop
Some of these changes can be made to the original loop, too:
let total = 0;
items?.forEach((item) => {
if (item?.quantity && item.price) {
total += item.quantity * item.price;
}
});
return total;
I can't see any advantage of the first over the second*. However the second is even faster then the first and looks more clean! The purpose of the first might be to demonstrate the use of built-in array-functions.
However mapreduce is used for a lot of Elements, so you might the speed it up as much as you can. This should be the fastest you can get:
const initialValue = 0;
let total = initialValue;
if (this.items) {
for (var i = this.items.length; i--;) {
let item = this.items[i]
if (item && item.quantity && item.price) {
total += item.quantity * item.price;
}
}
return total;
} else {
return initialValue
}
In addtion you could drop the if inside the loop, if you know that your array is consitant. Both ifs are just there to make sure the array is properly build and the script doesn't run into an Error, that would be usefull for userdata input, but in a closed system you don't need them.
*I noticed that, the second is missing the default value return initialValue