ReactJs functions that need refractoring (if possible) - javascript

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

Related

How to set state in loop

I'm trying to import Wordpress categories from .cvs file. I'm writing a simple app in react and I've got a function:
componentDidUpdate( prevProps, prevState ) {
let that = this
if (prevState.syncStatus !== this.state.syncStatus && this.state.syncStatus == 'posts') {
row_terms.forEach( (element, inx) => {
let parent = that.state.parent_id;
let _terms = element.split('>')
_terms = _terms.map(function(e){return e.trim();});
const total = _terms.length
_terms.forEach( (_term, index) => {
addCategory(_term, that.state.parent_id).then(result => {
let term_id
if( result.code && result.code == 'term_exists' ) {
term_id = result.data.resource_id
} else {
term_id = result.id
}
if ( ( 1 + index ) === total ) {
categories.push(term_id)
} else {
that.setState({parent_id: term_id})
}
})
})
})
}
}
and addCategory:
import WooCommerce from './woocommerce'
async function addCategory(name, parent) {
console.log('parent', parent)
try {
return await WooCommerce.postAsync('products/categories', {name: name, parent: parent}).then(result => {
return JSON.parse(result.toJSON().body);
});
} catch (e) {
return e
}
}
export default addCategory
and initial value is set
constructor(props) {
super()
this.state = {
parent_id: 0,
}
}
I want to add cateogories one after another because I need to set parent_id for categories that are children. So when it adds a category and the condition
if ( ( 1 + index ) === total ) {
is not met I want to set parent_id and then use it in next iteration. But it doesn't work.
When I use
console.log(that.state.parent_id)
after
that.setState({parent_id: term_id})
It prints correct value.
When i run my app it prints 0 ( console.log in addCategory() ) for all categories before any request is made.
.forEach() isn't aware of the asynchronicity of addCategory; you'll either have to chain all of those thens, or preferably just use async/await and plain old for loops, which can be used with await without extra hoops to jump through.
Assuming you want to loop through all of the row_terms and process them, you'll also need to await on those promises...
const categories = [];
const promises = row_terms.map(async (element) => {
const terms = element.split(">").map((e) => e.trim());
const total = terms.length;
let parent = null;
for (let i = 0; i < total; i++) {
const term = terms[i];
const result = await addCategory(term, parent);
const term_id = result.code && result.code === "term_exists" ? result.data.resource_id : result.id;
if (i === total - 1) {
categories.push(term_id);
} else {
parent = term_id;
}
}
});
await Promise.all(promises);

Having trouble stopping duplicate from being added to array

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
}
}

How to remove an object from Array in React

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

Rewrite a function to find an object by a matching id inside an array, update a value and set a react state

The function below receiving a rating value inside an object. While ID or Question stay intact, the rating value can be updated. As a result a React state value should be updated.
Is there a way to make this function look prettier/concise while just using a vanilla javascript.
ratingCompleted = ({ rating, question, id }) => {
let array = this.state.ratingResponses;
const index = array.findIndex(elem => elem.id == id);
if (index === -1) {
array.push({ rating, question, id });
this.setState({ ratingResponses: array });
} else {
array.map(object => {
if (object.id === id) {
object.rating = rating;
return object;
} else {
return object;
}
});
this.setState({ ratingResponses: array });
}
};
Make sure you spread to stop mutations
This could be a little cleaner but i thought I would show each step.
const array = [...this.state.ratingResponses]; // spread this so as to make a copy
let updatedArray;
const hasRatingAlready = array.some(item => item.id === id);
if (!hasRatingAlready) {
updatedArray = [...array, { rating, question, id }];
} else {
updatedArray = array.map(item => item.id === id ? {...item, rating} : item);
}
this.setState({ ratingResponses: updatedArray });

React Native Flatlist Not Rerendering Redux

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

Categories

Resources