I'm making Shopping Cart, and I need to show all elements which were added to Shopping Cart, but elements which are the same should appear in the list only 1 time , but in this case i need to show quantity of these elements...
{shoppingCart.map((book) => (
<div key={book.id} className="book-element">
<div className="col-1">
<img src={book.image}/>
</div>
<div className="col-4 shp-description">
<p>{book.title}</p>
<p>Cover: {book.hardCover === false ? 'Paperback' : 'Hardcover'}</p>
</div>
<div className="col-2">{}</div> // here i need to set quantity of the same elements
<div className="col-2">{book.price}$</div>
<div className="col-1">
<i className="fa fa-times" aria-hidden="true"></i>
</div>
</div>
))}
as #epascarello says, you need to process your list before mapping it to react components
processBookList(shoppingCart).map((book) => ...)
and then you have something like this
function processBookList(bookList) {
// iterate over your list of books and build a new array
// or maybe use array.reduce if it suits you
return proccessedBookList;
}
So process the books, basic task of combining records
getCartData() {
// loop over the cart
const combined = this.shoppingCart.reduce((obj, book) => {
// reference the book we already have or create a new record
obj[book.id] = obj[book.id] || { ...book, qty: 0 };
// increase the qty
obj[book.id].qty++;
return obj;
});
// return the array of books with qty
return Object.values(combined);
}
Related
The problem is I can't control the filter remember I have 20 products I need to establish if my category is not matched else product not found.
But my code product not found returned 20 times 🤨
see my output Screenshot
html = `<p>Product not found</p>` // just + sign remove
I am trying another way it returns a single time but, if my condition is true the product does not show like
`if (post.category === "men's clothing")`
How can I solve it?
// Men catagory
men.addEventListener('click', loadDataMen);
function loadDataMen() {
fetch('./src/db/db.json')
.then(function (response) {
return response.json();
})
.then(function (info) {
let html = '';
info.filter((post) => {
try {
if (post.category === "no match") {
html += `
<div class="single_product product-box bg-darkopacity rounded-20 position-relative mb-5 grid_view p-3">
<!--
Hover Zoom you can cancel if you don't like it just remove "hover-zoom" class
-->
<div class="hover-zoom drop-shadow-product position-relative">
<!-- PLaceholder Image -->
<img src="${post.image}" alt="Image" />
<!-- User Rating -->
<div class="show-rating">
<div class="rate">
${post.rating.rate} ⭐ | ${post.rating.count}
</div>
</div>
</div>
<!-- Product Wishlist -->
<div class="love bubbly-button">
<i class="fa-regular fa-heart"></i>
</div>
<!-- Product Offer -->
<div class="product-tag-warning badge bg-warning">${post.tag}</div>
<div class="product-functionality text-center mt-3">
<!-- Product title -->
<div class="product-title fw-bold text-break">
${post.title.substring(0, 18)}...
</div>
<!-- Product price -->
<div class="product-price mb-2"><strong>${post.price} only</strong></div>
<!-- Router navigation -->
<div class="two-btn-sm">
View
Buy
</div>
</div>
<!-- Product Description -->
<div class="discription">
<small class="text-decoration-underline">
<strong>Discription</strong>
</small>
<p class="p-0">
${post.description} SeeMore
</p>
</div>
</div>
`;
}
else{
html += `<p>Product not found</p>`
}
} catch (error) {
html = `<p>Somthing went wrong ${error}</p>`
}
})
output.innerHTML = html
})
.catch(function (error) {
console.log(error)
})
}
The problem is that you are using the filter method as a loop.
For more info about filter method check this out: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter
You must filter your posts result using a condition like so:
let filteredPosts=info.filter((post)=>post.category === "no match");
then doing something like that:
if(filteredPosts.length<=0){
html='Product not found'
}else{
filteredPosts.splice(4); //only 4 posts
for(let post of filteredPosts){
html+=....
}
}
Improved response:
What you have to do to filter by category or color or both, you must save which category and color the user has selected:
let selectedCategory=null;
let selectedColor=null;
let categoryItems=document.getElementByClassName('category'); //assuming categories items has a class category
for(let categoryItem of categoryItems){
//foreach category found
categoryItem.addEventListener('click', function(event){
selectedCatory=this.innerHTML; //get the clicked category
showFilteredItems();
});
}
let colorsItems=document.getElementByClassName('color');
//assuming colors items has a class color
for(let colorItem of colorsItems){
//foreach color found
categoryItem.addEventListener('click', function(event){
selectedColor=this.innerHTML; //get the clicked color
showFilteredItems();
});
}
//this function is your old method
function showFilteredItems(){
....
fetch(...)
....
//by default all results
let filteredPosts=info;
//category filter
if(selectedCategory){
filteredPosts=filteredPosts.filter((post)=>post.category === selectedCategory);
}
//color filter
if(selectedColor){
filteredPosts=filteredPosts.filter((post)=>post.color=== selectedColor);
}
//show the items
if(filteredPosts.length<=0){
html='Product not found'
}else{
filteredPosts.splice(4); //only 4 posts
for(let post of filteredPosts){
html+=....
}
}
}
Keep in mind that in production application, we prefer to send filters(category and color) to the backend of the application that way you will not receive all the items (this can be really slow if you have thousands of items) but only the filtered.
In my angular application I have some iteration items and saving the items based on adding the items.
.component.html
<ng-container *ngFor="let categoryDetail of selectedCategoryDetails">
<div class="__header">
<div>
<b>{{ categoryDetail.category }}</b>
</div>
</div>
<div
class="clinical-note__category__details"
*ngIf="categoryDetail.showDetails">
<ul>
<li class="habit-list"
*ngFor="let habits of categoryDetail.habitDetails" >
<div class="target-details">
<b>{{ clinicalNoteLabels.target }}: </b
><span class="habit-list__value">{{ habits.target }}</span>
</div>
</li>
</ul>
<div class="habit-footer">
<span class="m-l-10"
[popoverOnHover]="false"
type="button"
[popover]="customHabitPopovers"><i class="fa fa-trash-o" ></i> Delete</span>
</div>
<div class="clinical-note__popoverdelete">
<popover-content #customHabitPopovers [closeOnClickOutside]="true">
<h5>Do you want to delete this habit?</h5>
<button
class="btn-primary clinical-note__save" (click)="deletedata(index);customHabitPopovers.hide()">yes </button>
</popover-content></div>
</div>
</ng-container>
.component.ts
public saveHealthyHabits() {
let isCategoryExist = false;
let categoryDetails = {
category: this.clinicalNoteForm.controls.category.value,
habitDetails: this.healthyHabits.value,
showDetails: true,
};
if (this.customHabitList.length) {
categoryDetails.habitDetails = categoryDetails.habitDetails.concat(
this.customHabitList
);
this.customHabitList = [];
}
if (this.selectedCategoryDetails) {
this.selectedCategoryDetails.forEach((selectedCategory) => {
if (selectedCategory.category === categoryDetails.category) {
isCategoryExist = true;
selectedCategory.habitDetails = selectedCategory.habitDetails.concat(
categoryDetails.habitDetails
);
}
});
}
if (!this.selectedCategoryDetails || !isCategoryExist) {
this.selectedCategoryDetails.push(categoryDetails);
}
this.clinicalNoteForm.patchValue({
category: null,
});
this.healthyHabits.clear();
}
public deletedata(index:number){
if (this.selectedCategoryDetails) {
this.selectedCategoryDetails.forEach((selectedCategory) => {
this.selectedCategoryDetails.splice(index, 1);
}}
From the above code I have saved the data based on adding the items as above and my requirement is when we click on the delete(it will show the popup having the button yes implemented in anbove code).
when we click on the yes button from list of items, I have to remove the particular item
When I tried removing ,It is only deleting the first item instead of clicked one
Can anyone help me on the same
The logic for deletion is incorrect. The splice mutates the original array, and you are applying the loop for deletion, which keeps on iterating over the array and deleting the array elements based on index, instead of deleting single matched index element.
Example -
const categories = [
1,
2,
3,
4
];
function removal(i) {
categories.forEach((category, index) => {
categories.splice(i, 1);
});
console.log('----Categories-->', categories);
}
removal(0);
Categories Array
First Iteration [index = 0]
[1,2,3,4]
Loop Starts Iterating from 1
Second Iteration [index = 1]
[2,3,4]
Loop Starts Iterating from 3
Third Iteration [index = 2]
[3,4]
Stop
Instead you can use filter function.
public deletedata(index:number){
this.selectedCategoryDetails = this.selectedCategoryDetails.filter((_, i) => i! == index);
}
Note - I would recommend to delete the categories based on some identifier like id instead of index because the array elements position can get changed.
Instead of passing index to deleteData method, you can pass the category object.
public deletedata(category){
this.selectedCategoryDetails = this.selectedCategoryDetails.filter((c) => c.id! == category.id);
}
I am kind of confused when applying the key parameter to the below code in ReactJS, i understand we need to use the key parameter to uniquely identify each DOM element, when i tried to use key={index} in buttons it comes up with error(Encountered two children with the same key).
I am also a bit puzzled here, as updating the setQuantity update all the items in all products(as shown in screenshot below), not sure how i can use the unique key item to update each product quantity only.
Any help appreciated.
Update 1: the code is now updated with key={productName}, but still the adding/ subtracting value is updating across all products when adding/subtracting single product
function Home({ props }) {
//quantitiyselected, to set the no. of items purchased
const [quantitiyselected, setQuantity] = useState(0);
// below function to add, subtract quantity
let selectquantity = (e) => {
if (e.currentTarget.name == "add") {
let i = quantitiyselected + 1;
setQuantity(i);
} else if (e.currentTarget.name == "subtract" && quantitiyselected > 0) {
let z = quantitiyselected - 1;
setQuantity(z);
} else;
};
return (
<div className="products">
{props.map((eachproduct, index) => {
let productName = eachproduct.product_name;
let producNumber = eachproduct.producNumber;
let price = eachproduct.price;
let desc = eachproduct.productDescription;
let photo = eachproduct.image_URL;
let stockQuantity = eachproduct.stockQuantity;
return (
<div className="products" key={productName }>
<ul>
<li>
<img className="products-image" src={photo} />
</li>
<li>{productName} </li>
<li>
Item No:{producNumber}(InStock:{stockQuantity})
</li>
<li>price:{price}£ </li>
<li>{desc}</li>
<li>
<ButtonGroup aria-label="quantityofproduct">
<Button
variant="secondary"
name="subtract"
value="subtract"
onClick={selectquantity}
>
-
</Button>
<Button variant="secondary">
{quantitiyselected}
</Button>
<Button
variant="secondary"
name="add"
value="add"
onClick={selectquantity}
>
+
</Button>
</ButtonGroup>
variant="primary">
Buy
</Button>
</li>
</ul>
</div>
);
})}
</div>
);
}
export default Home;
You are applying key={index} to multiple elements in your render (<div>, <ul> and each <Button />), but it only needs to go on the top level element that is being rendered in the array which in your case is the <div className="products">.
As an aside, an array index is generally not a good candidate for the key - it should be unique to each item that is being rendered. In your example, I think producNumber would be the best bet as I am assuming it is some unique identifier to each product.
remove key={index} from everything that you have it on
add key={producNumber} to the inner <div className="products">
This is a good resource to learn more about React's key prop.
The reason every product has the same quantity, is you only have a single number for your state:
React.useState(0);
You are trying to track quantities per product, so you would need a quantity for each product. One way you could do that is by using an object ({}) that has properties of your producNum and quantity as their value. For example:
const products = [
{ name: "thing", num: 152 },
{ name: "other-thing", num: 254 },
];
const initialQuantities = products.reduce(
(quantities, product) => ({ ...quantities, [product.num]: 0 }),
{}
);
console.log(initialQuantities);
This creates the object with each product with an initial quantity of 0.
When you then increase or decrease the quantity, you would pass the product number of the product you clicked to the function, and then you can set the new quantity of just that product, while leaving the other product quantities untouched by using Spread syntax (...):
const initialQuantities = products.reduce(
(quantities, product) => ({ ...quantities, [product.producNumber]: 0 }),
{}
);
const [quantities, setQuantities] = React.useState(initialQuantities);
const increase = (productNum) => {
setQuantities({
...quantities,
[productNum]: quantities[productNum] + 1,
});
};
const decrease = (productNum) => {
setQuantities({
...quantities,
[productNum]: Math.max(0, quantities[productNum] - 1),
});
};
and then your onClick would become:
onClick={() => increase(producNumber)}
and
onClick={() => decrease(producNumber)}
A simple implementation can be seen here: https://codesandbox.io/s/icy-dust-nbetq?file=/src/App.js:175-655
I'm making weather app for 7 days and I need to create abillity for users to open some of days to see more information about weather of current day. So, it means that every item choosen by user has to contain unique id (i don't know could I use in this situation index instead of id) to show information only about some of day. So before this I had some code:
const DailyWeatherData = ({dailyData, isLoading}) => {
const getWeatherStatistic = (value0,value1) => {
if(dailyData && Array.isArray(dailyData.daily)){
return (
<div className="col-lg-3 box-daily-weather">
<NavLink to={'/WeatherDayStat'}>
{setDay([value1])}
<div className="temp-day">{Math.ceil(dailyData.daily[value0].temp.day)}°C</div>
<div className="feels-like">Feels like: {Math.ceil(dailyData.daily[value0].feels_like.day)}°C</div>
<div className="daily-weather-condition">Conditions: {dailyData.daily[value0].weather.map(e => e.main)}</div>
</NavLink>
</div>
)
}else {
return isLoading === true ? <Preloader /> : null
}
}
const setDay = (param) => {
return checkCod ? null : setCurrentDate(new Date(),param)
}
return (
<div>
<div className="daily-weather-container">
{checkCod ? null : <div className="daily-title">Daily Weather</div>}
<div className='row scrolling-wrapper justify-content-center'>
{getWeatherStatistic(1,1)}
{getWeatherStatistic(2,2)}
{getWeatherStatistic(3,3)}
{getWeatherStatistic(4,4)}
{getWeatherStatistic(5,5)}
{getWeatherStatistic(6,6)}
</div>
</div>
</div>
)
}
export default DailyWeatherData
In function getWeatherStatistic you can see 2 arguments, value0 - doesn't matter here, value1 - using to show only 1st-6 object, because array which i have got from ajax request contains more days(10) but I need to show only 6 of them. And most importantly, each of them is separate. Logically I'd use map, but It shows all items, so I can use slice but it also shows 6 items in 1 column.
The next problem, this array has not contain parameter like ID, that's why I can't add to NavLink id. If there were ID, I would make something like that dailyData.daily.map(p => <NavLink to={'/WeatherDayStat' + p.id}>)
Also, just in case, add the state code (daily - array which I need):
So I have 2 questions:
How to show only 6 days from array?
How to add unique ID to every Item from array?
Making something like this:
//making new array with data I need
let sliceDailyData = dailyData.daily ? dailyData.daily.slice(1,7) : null
//using map with new array
{sliceDailyData ? sliceDailyData.map((i) => <div key={i.dt} className="col-lg-3 box-daily-weather">
{getCurrentDate(new Date())}
<NavLink to={'/WeatherDayStat'}>
<div className="temp-day">{Math.ceil(i.temp.day)}°C</div>
<div className="feels-like">Feels like: {Math.ceil(i.feels_like.day)} °C</div>
<div className="daily-weather-condition">Conditions: {i.weather.map(i => i.main)} </div>
</NavLink>
</div>
): null}
Speaking about ID, I have created variable which generte id and push it to array.
You can store your dailyData.daily array in an object and then retrieve it with key, you can use something simple but SEO friendly like obj['day'+(dailyData.daily.indexOf(d)+1)]
obj{}
dailyData.daily.forEach(d=>array.indexOf(d)<=5?obj[String(Math.random())]=d:null)
I have a news bar and I used ngx-newsticker, I need to attach a tooltip for each item in the news bar. However, the plugin takes an array of string as shown below.
I tried to loop throw the array but it doesn't work:
<div class="M_announcementDiv" *ngIf="announcementsArrInTicker && announcementsArrInTicker.length>0">
<div class="container">
<div placement="bottom" [ngbTooltip]="announcementsArrInTicker">
<ngx-newsticker title="{{'ANNOUNCEMENTS'|translate}}" [events]="announcementsArrInTicker" [interval]="interval"></ngx-newsticker>
</div>
</div>
</div>
I need to push the current item to the tooltip in this line instead of pushing
the whole array.
<div placement="bottom" [ngbTooltip]="announcementsArrInTicker">
and this is the ts function that retrieves the data:
getGeneralAnnouncements() {
// this.isAuthenticated = this.authService.isAuthenticated();
this.isAuthenticated =false;
this.newsService.searchGeneralAnnouncements(this.searchModel, this.isAuthenticated).subscribe(res => {
this.announcementArr = res;
res.announcements.map((announcement) => {
if(this.translate.currentLang=="ar"){
this.announcementsArrInTicker.push(announcement.bodyAr);
}else{
this.announcementsArrInTicker.push(announcement.bodyEn);
}
});
}, error => {
// this.businessException = errorsUtility.getBusinessException(error); //this.notificationService.showNotification(this.businessException.message,
NotificationType.Error);
})
}