I'm new to these stuff but I am passionate about learning it. So feel free to link documentations, I would gladly look up for them. I have built a cart list component in Reactjs. I implemented addToCart and removeFromCart functions. The problem lies in removeFromCart function. I have a json file from which I get my categories and products. I have onClick declerations that change the state of the component and render the new product list for the desired category. I added a button that removes products from cart but the button only decrease the quantity of the product. I want to remove the product when its quantity drops below zero. Here is my code, I hope you could help.
changeCategory = (category) => {
this.setState({ currentCategory: category.categoryName });
this.getProducts(category.id);
};
resetCategory = (category) => {
this.setState({currentCategory: "",});
this.getProducts()
};
getProducts = (categoryId) => {
let url = "http://localhost:3000/products";
if (categoryId) {
url += "?categoryId=" + categoryId;
}
fetch(url)
.then((response) => response.json())
.then((data) => this.setState({ products: data }));
};
addToCart = (product) => {
let newCart = this.state.cart;
var addedItem = newCart.find((c) => c.product.id === product.id);
if (addedItem) {
addedItem.quantity += 1;
} else {
newCart.push({ product: product, quantity: 1 });
}
this.setState({ cart: newCart });
alertify.success(product.productName + " added to the cart.,", 2);
};
This is what the states of component looks like:
state = {
currentCategory: "",
products: [],
cart: [],
};
And lastly the problematic part:
removeFromCart = (product) => {
let newCart = this.state.cart;
var addedItem = newCart.find((c) => c.product.id === product.id);
if (addedItem) {
addedItem.quantity -= 1;
}
// var zeroItem = addedItem.quantity ;
// zeroItem = newCart.filter((a) => a.addedItem.quantity !== 0)
// this.state.zeroItem.filter((c) => c.product.id !== product.id);
this.setState({ cart: newCart });
alertify.error(product.productName + " removed from the cart.", 2);
};
Comment section was what I tried but they didn't work, obviously. How can I remove a product in a project like this when its quantity drops below zero?
Thanks everyone in advance.
For those who would help: I have no problem for decreasing the quantity. I just need a way to remove that specific object it when the addedItem's quantity drops below 0.
You can simply use a filter to remove an item from the array.
removeFromCart = (product) => {
let newCart = this.state.cart;
var addedItem = newCart.find((c) => c.product.id === product.id);
if (addedItem && addedItem.quantity > 1) {
addedItem.quantity -= 1;
} else {
newCart = newCart.filter(({ id }) => id === product.id)
}
// var zeroItem = addedItem.quantity ;
// zeroItem = newCart.filter((a) => a.addedItem.quantity !== 0)
// this.state.zeroItem.filter((c) => c.product.id !== product.id);
this.setState({ cart: newCart });
alertify.error(product.productName + " removed from the cart.", 2);
};
I just edited your code a little and now it works like a charm. You can see the revised version of mine below.
removeFromCart = (product) => {
let newCart = this.state.cart;
var addedItem = newCart.find((c) => c.product.id === product.id);
if (addedItem && addedItem.quantity > 1) {
addedItem.quantity -= 1;
} else {
newCart = newCart.filter((c) => c.product.id !== product.id);
this.setState({ cart: newCart });
}
this.setState({ cart: newCart });
alertify.error(product.productName + " removed from the cart.", 2);
};
removeFromCart = (product) => {
let {cart}=this.state;
let {quantity:currentQuantity} = cart.find(pr => pr.product ===product)
if(currentQuantity > 1) {
this.setState(prevState =>
({cart : [...prevState.cart.filter(pr => pr.product !== product), {product, quantity: currentQuantity-1} ] }))
}
else {
this.setState(prevState =>
({cart : prevState.cart.filter(pr => pr.product !== product) }))
}
}
Related
I am using React. On click of a button, the following function is executed:
const completeTaskHandler = (idValue) => {
setData((prevData) => {
const updatedData = [...prevData];
const updatedItem = updatedData.filter((ele) => ele.id === idValue)[0];
updatedItem.completed = true;
const newData = updatedData.filter((ele) => ele !== updatedItem);
newData.unshift(updatedItem);
return newData;
});
};
My data is an array of objects like this:
[{userId: 1, id: 2, title: "task 1", completed: true}, .....].
Basically I want to move the updated item to the start of the array. Is there any better solution for this?
updatedItem should not be mutated. And this string const newData = updatedData.filter((ele) => ele !== updatedItem); is not fine. You can do it like this :
const completeTaskHandler = (idValue) => {
setData((prevData) => {
const targetItem = prevData.find((ele) => ele.id === idValue);
const updatedItem = { ...targetItem, completed: true };
const filteredData = prevData.filter((ele) => ele.id !== idValue);
return [updatedItem, ...filteredData];
});
};
Even better to reducing an extra filter:
const completeTaskHandler = (idValue) => {
setData((prevData) => {
const targetIndex = prevData.findIndex((ele) => ele.id === idValue);
return [{ ...prevData[targetIndex], completed: true }].concat(prevData.slice(0, targetIndex + 1)) .concat(
prevData.slice(targetIndex + 1)
)
});
};
First find index of updated element using Array.findIndex(), then remove the same element using Array.splice() and add it to front of the array.
const completeTaskHandler = (idValue) => {
setData((prevData) => {
const updatedData = [...prevData];
const index = updatedData.findIndex(obj => obj.id === idValue);
const [updatedItem] = updatedData.splice(index, 1);
updatedItem.completed = true;
updatedData.unshift(updatedItem);
return updatedData;
});
};
The simplest one with only one forEach.
const completeTaskHandler = idValue => {
setData(prevData => {
let updatedItem = {}, newData = [];
prevData.forEach((ele) => {
if (ele.id === idValue) {
updatedItem = ele;
updatedItem.completed = true;
} else {
newData.push(ele);
}
});
newData.unshift(updatedItem);
return newData;
});
};
I have added an voice command prompt to my small ecommerce application. When I command the ai to add a product to the list it adds it and when I try to add it again it doesn't. However, when I try to add a different item the command prompt denies it. I want it to work for all item. I have tried to look up different options including indexOf but that method isn't working either.
function voiceCommand(data) {
const products = new Products();
let alanBtnInstance = alanBtn({
top: '15px',
left: '15px',
onCommand: function (commandData) {
if (commandData.command === "opencart") {
products.openCart();
}
else if (commandData.command === "closecart") {
products.closeCart();
}
else if (commandData.command === "addItem") {
// get cart Items to compare to commandData.name
const cartItem = data
cartItem.forEach(item => {
return item.amount = 1
});
const item = cartItem.map(item => item)
.find(item => item.title.toLowerCase() === commandData.name.toLowerCase());
cart = [...cart, item]
function hasDuplicates(arr) {
return new Set(arr).size !== arr.length;
}
if (hasDuplicates(cart)) {
alanBtnInstance.playText(`${item.title} is already in cart`);
return
}
else {
const buttons = [...document.querySelectorAll('.cart-btn')]
buttonsDOM = buttons;
buttons.forEach(button => {
let id = button.dataset.id;
let inCart = cart.find(item => item.id === Number(id));
if (inCart) {
button.innerText = "In Cart";
button.disabled = true;
}
});
products.addCartItem(item);
products.setCartValues(cart);
products.openCart()
return
}
}
},
rootEl: document.getElementById("alan-btn"),
});
}
I simplified the code a little to show the basic principle (I hope I was not mistaken in the logic)
let cart = [];
const addItem = (data, commandData) => {
const item = data
.find(item => item.title.toLowerCase() ===
commandData.name.toLowerCase());
const dup = cart
.find(item => item.title.toLowerCase() ===
commandData.name.toLowerCase());
if (dup) {
//console.log(`${dup.title} is already in cart`)
dup.amount += 1; // just change amount
item.amount -= 1; // calc rest in store
} else {
cart = [...cart, {...item, amount: 1}] // insert new
item.amount -= 1; // calc rest in store
}
}
// store with amounts
const data = [
{id:'0', amount: '100', title: 'a'},
{id:'0', amount: '100', title: 'b'}
]
console.log('Cart before:')
console.log(JSON.stringify(cart))
console.log('Store before:')
console.log(JSON.stringify(data))
// test actions:
addItem(data, {name: 'b'})
addItem(data, {name: 'b'})
addItem(data, {name: 'a'})
console.log('Cart after:')
console.log(JSON.stringify(cart))
console.log('Store after:')
console.log(JSON.stringify(data))
I think your code should be like this:
else if (commandData.command === "addItem") {
// get cart Items to compare to commandData.name
const cartItem = data;
cartItem.forEach(item => {
return item.amount = 1
});
const item = cartItem.find(item => item.title.toLowerCase() === commandData.name.toLowerCase());
const dup = cart.find(item => item.title.toLowerCase() === commandData.name.toLowerCase());
if (dup) {
alanBtnInstance.playText(`${item.title} is already in cart`);
return
}
else {
cart = [...cart, item]
const buttons = [...document.querySelectorAll('.cart-btn')]
buttonsDOM = buttons;
buttons.forEach(button => {
let id = button.dataset.id;
let inCart = cart.find(item => item.id === Number(id));
if (inCart) {
button.innerText = "In Cart";
button.disabled = true;
}
});
products.addCartItem(item);
products.setCartValues(cart);
products.openCart()
return
}
}
I have a few functions I've written for a basic e-commerce store, but both seem a little lengthy to me.
Perhaps someone out there has some suggestions on how I might be able to refractor them?
Cheers.
1 - a function that had adds an item to the cart or wishlist based on an action:
addToFunnelHandler = (uuid, price, title, action) => {
let productToAdd = {}
productToAdd.uuid = uuid
productToAdd.price = price
productToAdd.title = title
if (action === 'bag') {
let productsInBag = [...this.state.productsInBag]
productsInBag.push(productToAdd)
this.setState({ productsInBag: productsInBag, bagCount: this.state.bagCount + 1, bagTotal: this.state.bagTotal + price })
} else if (action === 'wishlist') {
let productsInWishlist = [...this.state.productsInWishlist]
productsInWishlist.push(productToAdd)
this.setState({ productsInWishlist: productsInWishlist, wishlistCount: this.state.wishlistCount + 1})
}
}
2 - Same but in reverse, removing an item from the funnel:
removeFromFunnelHandler = (uuid, price, action) => {
if (action === 'bag') {
let productsInBag = [...this.state.productsInBag]
productsInBag.forEach((product, index) => {
if (product.uuid === uuid) { productsInBag.splice(index, 1) }
})
this.setState({ productsInBag: productsInBag, bagCount: this.state.bagCount - 1, bagTotal: this.state.bagTotal - price })
} else if (action === 'wishlist') {
let productsInWishlist = [...this.state.productsInWishlist]
productsInWishlist.forEach( (product, index) => {
if (product.uuid === uuid) { productsInWishlist.splice(index, 1) }
})
this.setState({ productsInWishlist: productsInWishlist, wishlistCount: this.state.wishlistCount - 1 })
}
}
As for the first function, you could remove most of the declarations:
Declare productToAdd in one go
Rather than pushing, just include the value in the array
addToFunnelHandler = (uuid, price, title, action) => {
let productToAdd = { uuid, price, title }
if (action === 'bag') {
this.setState({ productsInBag: [...this.state.productsInBag, productToAdd], bagCount: this.state.bagCount + 1, bagTotal: this.state.bagTotal + price })
} else if (action === 'wishlist') {
this.setState({ productsInWishlist: [...this.state.productsInWishlist, productToAdd], wishlistCount: this.state.wishlistCount + 1 })
}
}
For the second, there's not much to refactor without more context:
Use inline if statements
Use shorthand property assiging
Use filter over forEach
removeFromFunnelHandler = (uuid, price, action) => {
if (action === 'bag') {
const productsInBag = [...this.state.productsInBag].filter(product => product.uuid !== uuid)
this.setState({ productsInBag, bagCount: this.state.bagCount - 1, bagTotal: this.state.bagTotal - price })
} else if (action === 'wishlist') {
const productsInWishlist = [...this.state.productsInWishlist].filter(product => product.uuid !== uuid)
this.setState({ productsInWishlist, wishlistCount: this.state.wishlistCount - 1 })
}
}
In my E-Commerce project, the products are stored in localStorage. componentDidMount() gets all these products from localStorage and displays it. How to state the state or condition when no products are available.
componentDidMount = () => {
this.setProducts();
// Gets all products in cart from localStorage
this.setState(
() =>{
return {cart: JSON.parse(localStorage.getItem('myCart'))}
},
() => {
this.addTotal()
})
// How to set the condition when no products in Cart?
}
// Set all the products on Main Page
setProducts = () => {
let tempProducts = [];
storeProducts.forEach(item => {
const singleItem = {...item};
tempProducts = [...tempProducts, singleItem];
});
this.setState(() => {
return {products: tempProducts};
});
};
// Here products are added to cart and stored in localStorage
addToCart = (id) => {
let tempProducts = [...this.state.products];
const index = tempProducts.indexOf(this.getItem(id));
const product = tempProducts[index];
product.inCart = true;
product.count = 1;
const price = product.price;
product.total = price;
this.setState(() => {
return { products: tempProducts, cart: [...this.state.cart,
product] };
},
() => {
this.addTotal();
localStorage.setItem('myCart', JSON.stringify(this.state.cart))
});
}
I have also tried to make following changes, but, no effect. In componentDidMount()
componentDidMount() {
if(this.state.cart.length > 0) {
this.setState(
() =>{
return {cart: JSON.parse(localStorage.getItem('myCart'))}
},
() => {
this.addTotal()
})
} else {
this.setState(() => {
return {cart:[]}
})
}
}
// Clear Cart
clearCart = () => {
this.setState(() => {
return {cart:[]}
}, () => {
this.setProducts();
this.addTotal();
})
localStorage.removeItem('myCart')
}
When I remove code of setState (shown in the beginning) from componentDidMount() displays empty cart message, which is expected else, if the cart is cleared and refreshed browser throws 'cart.length' error. Any possible solution?
JSON.parse will return an object. It depends on your data structure but there is no cart.lendth for the object. So that is your first problem. So for the below example, I store the parsed value as an array.
Also, if state.cart is not initiated, there is no .length property for it.
For your second problem have a look at the below version of your componentDidMount:
componentDidMount() {
if(Array.isArray(this.state.cart) && this.state.cart.length) {
const cart = localStorage.getItem('myCart') || [];
this.setState({cart: [JSON.parse(cart)]}), this.addTotal);
} else {
this.setState({ cart:[] });
}
}
Again, it depends on your implementation, but you might need to initiate the component's state with cart: localStorage.getItem('myCart') || [] or doing what I have done above. I'm basically checking if cart is an array && it has length then parse it otherwise initiate the array.
Finally I got the solution as below
const cart = localStorage.getItem('myCart')
this.setState({cart: JSON.parse(cart) ? JSON.parse(cart) : []}, this.addTotal)
Just modified the code and works perfectly without any issues
My FlatList does not update when the props I pass from redux change. Every time I send a message I increase everyones unread message count in both firebase and in my redux store. I made sure to include key extractor and extra data, but neither helps. The only thing that changes the unread message count is a reload of the device. How do I make sure the flatList updates with MapStateToProps. I made sure to create a new object by using Object.Assign:
action:
export const sendMessage = (
message,
currentChannel,
channelType,
messageType
) => {
return dispatch => {
dispatch(chatMessageLoading());
const currentUserID = firebaseService.auth().currentUser.uid;
let createdAt = firebase.database.ServerValue.TIMESTAMP;
let chatMessage = {
text: message,
createdAt: createdAt,
userId: currentUserID,
messageType: messageType
};
FIREBASE_REF_MESSAGES.child(channelType)
.child(currentChannel)
.push(chatMessage, error => {
if (error) {
dispatch(chatMessageError(error.message));
} else {
dispatch(chatMessageSuccess());
}
});
const UNREAD_MESSAGES = FIREBASE_REF_UNREAD.child(channelType)
.child(currentChannel).child('users')
UNREAD_MESSAGES.once("value")
.then(snapshot => {
snapshot.forEach(user => {
let userKey = user.key;
// update unread messages count
if (userKey !== currentUserID) {
UNREAD_MESSAGES.child(userKey).transaction(function (unreadMessages) {
if (unreadMessages === null) {
dispatch(unreadMessageCount(currentChannel, 1))
return 1;
} else {
alert(unreadMessages)
dispatch(unreadMessageCount(currentChannel, unreadMessages + 1))
return unreadMessages + 1;
}
});
} else {
UNREAD_MESSAGES.child(userKey).transaction(function () {
dispatch(unreadMessageCount(currentChannel, 0))
return 0;
});
}
}
)
})
};
};
export const getUserPublicChannels = () => {
return (dispatch, state) => {
dispatch(loadPublicChannels());
let currentUserID = firebaseService.auth().currentUser.uid;
// get all mountains within distance specified
let mountainsInRange = state().session.mountainsInRange;
// get the user selected mountain
let selectedMountain = state().session.selectedMountain;
// see if the selected mountain is in range to add on additional channels
let currentMountain;
mountainsInRange
? (currentMountain =
mountainsInRange.filter(mountain => mountain.id === selectedMountain)
.length === 1
? true
: false)
: (currentMountain = false);
// mountain public channels (don't need to be within distance)
let currentMountainPublicChannelsRef = FIREBASE_REF_CHANNEL_INFO.child(
"Public"
)
.child(`${selectedMountain}`)
.child("Public");
// mountain private channels- only can see if within range
let currentMountainPrivateChannelsRef = FIREBASE_REF_CHANNEL_INFO.child(
"Public"
)
.child(`${selectedMountain}`)
.child("Private");
// get public channels
return currentMountainPublicChannelsRef
.orderByChild("key")
.once("value")
.then(snapshot => {
let publicChannelsToDownload = [];
snapshot.forEach(channelSnapshot => {
let channelId = channelSnapshot.key;
let channelInfo = channelSnapshot.val();
// add the channel ID to the download list
const UNREAD_MESSAGES = FIREBASE_REF_UNREAD.child("Public")
.child(channelId).child('users').child(currentUserID)
UNREAD_MESSAGES.on("value",snapshot => {
if (snapshot.val() === null) {
// get number of messages in thread if haven't opened
dispatch(unreadMessageCount(channelId, 0));
} else {
dispatch(unreadMessageCount(channelId, snapshot.val()));
}
}
)
publicChannelsToDownload.push({ id: channelId, info: channelInfo });
});
// flag whether you can check in or not
if (currentMountain) {
dispatch(checkInAvailable());
} else {
dispatch(checkInNotAvailable());
}
// if mountain exists then get private channels/ if in range
if (currentMountain) {
currentMountainPrivateChannelsRef
.orderByChild("key")
.on("value", snapshot => {
snapshot.forEach(channelSnapshot => {
let channelId = channelSnapshot.key;
let channelInfo = channelSnapshot.val();
const UNREAD_MESSAGES = FIREBASE_REF_UNREAD.child("Public")
.child(channelId).child('users').child(currentUserID)
UNREAD_MESSAGES.on("value",
snapshot => {
if (snapshot.val() === null) {
// get number of messages in thread if haven't opened
dispatch(unreadMessageCount(channelId, 0));
} else {
dispatch(unreadMessageCount(channelId, snapshot.val()));
}
}
)
publicChannelsToDownload.push({ id: channelId, info: channelInfo });
});
});
}
return publicChannelsToDownload;
})
.then(data => {
setTimeout(function () {
dispatch(loadPublicChannelsSuccess(data));
}, 150);
});
};
};
Reducer:
case types.UNREAD_MESSAGE_SUCCESS:
const um = Object.assign(state.unreadMessages, {[action.info]: action.unreadMessages});
return {
...state,
unreadMessages: um
};
Container- inside I hook up map state to props with the unread messages and pass to my component as props:
const mapStateToProps = state => {
return {
publicChannels: state.chat.publicChannels,
unreadMessages: state.chat.unreadMessages,
};
}
Component:
render() {
// rendering all public channels
const renderPublicChannels = ({ item, unreadMessages }) => {
return (
<ListItem
title={item.info.Name}
titleStyle={styles.title}
rightTitle={(this.props.unreadMessages || {} )[item.id] > 0 && `${(this.props.unreadMessages || {} )[item.id]}`}
rightTitleStyle={styles.rightTitle}
rightSubtitleStyle={styles.rightSubtitle}
rightSubtitle={(this.props.unreadMessages || {} )[item.id] > 0 && "unread"}
chevron={true}
bottomDivider={true}
id={item.Name}
containerStyle={styles.listItemStyle}
/>
);
};
return (
<View style={styles.channelList}>
<FlatList
data={this.props.publicChannels}
renderItem={renderPublicChannels}
keyExtractor={(item, index) => index.toString()}
extraData={[this.props.publicChannels, this.props.unreadMessages]}
removeClippedSubviews={false}
/>
</View>
);
}
}
Object.assign will merge everything into the first object provided as an argument, and return the same object. In redux, you need to create a new object reference, otherwise change is not guaranteed to be be picked up. Use this
const um = Object.assign({}, state.unreadMessages, {[action.info]: action.unreadMessages});
// or
const um = {...state.unreadMessages, [action.info]: action.unreadMessages }
Object.assign() does not return a new object. Due to which in the reducer unreadMessages is pointing to the same object and the component is not getting rerendered.
Use this in your reducer
const um = Object.assign({}, state.unreadMessages, {[action.info]: action.unreadMessages});