Set state based on value of array item in previous state - javascript

Hello I am new to programming and I am trying to make a function in React that adds a team to an array of selected teams but only if there are less than 2 teams selected and if the team is available (not already chosen). It is working to limit the array to only 2 values but the array will accept the same team twice and I am not sure why. For example if the user clicks ATL twice then that team will be in the array twice. What did I do wrong here and what should I change in order to fix it? Sorry if the question is too simple for this forum, I am new.
Here is the code where I am changing the state and checking if gameState.selected_teams[0] != team:
function App() {
const [gameState, setGameState] = useState({
cards: Cards,
selected_teams: []
});
function setTeam(team){
if (gameState.selected_teams.length < 2 && gameState.selected_teams[0] != team) {
setGameState((prevState) => ({
cards: prevState.cards,
selected_teams: [
...prevState.selected_teams, {
team
}
]
}))
}
}
And for the component that calls the setTeam function:
function TeamSelect({cards, setTeam}) {
var teams = Object.keys(cards);
return (
<div className='flex-container'>
<div className='team-container'>
{
teams.map(team => (
<div
onClick={() => setTeam(team)}
className='team-label' key={team}>
{team}
</div>
))
}
</div>
</div>
)
}

You're adding an object {team: team} to your selected teams array each time you perform your click here:
selected_teams: [
...prevState.selected_teams, {
team
}
]
but your team key that you pass into your setTeam function is a string, so your comparison fails as you're trying to compare a string with an object. You can change your comparison to extract the team property from your object:
gameState.selected_teams[0]?.team != team
The ? ensures that a value exists at index 0 in your array before using .team on it (otherwise you would get an error if it's undefined).
You can adapt this code to handle more than one object by using .every() to check that all objects in selected_team's aren't equal to the one you're adding:
if(gameState.selected_teams.length < 2 && gameState.selected_teams.every(obj => obj.team != team)
If you don't need to pass an object {team: team} (as an object with one property doesn't add much value), then you can simply push your team string into your selected teams, and use .includes() to check if the team you're adding already exists in the array:
selected_teams: [
...prevState.selected_teams, team
]
you can then update your condition to use .includes():
if(gameState.selected_teams.length < 2 && !gameState.selected_teams.includes(team))

Related

Can't get mutator logic to correctly add the proper amount of items into a cart

I'm trying to make an add to cart function that first checks if the item being added is already in the cart. If it's in the cart, update its quantity property. If not in the cart, add the entire object to the cart. I think my problem is I'm getting the logic wrong inside my "ADD_ITEM_TO_CART" mutator function.
This is my store with some console.logs() from when I click "addToCart()"
state: {
checkoutCart: [],
},
actions: {
cartAdd({ commit }, payload) {
commit("ADD_ITEM_TO_CART", payload);
},
},
mutations: {
ADD_ITEM_TO_CART(state, payload) {
//CONSOLE.LOG()'s
console.log("state.checkoutCart[0]", state.checkoutCart[0]);
// eslint-disable-next-line
console.log("state.checkoutCart[0].item", state.checkoutCart.item);
console.log("state.checkoutCart", state.checkoutCart);
//IF ITEM ALREADY IN checkoutCart, UPDATE IT'S QUANTITY
if (state.checkoutCart.includes(payload.item)) {
state.checkoutCart.quantity += payload.quantity;
console.log("Item already in cart");
}
//IF ITEM NOT IN checkoutCart, UPDATE THE QUANTITY PROPERTY AND ADD ITEM TO CART
else {
payload.item.quantity = payload.quantity;
state.checkoutCart.push(payload);
}
https://i.imgur.com/rjOOljN.png
I thought this code would work, but it ALWAYS executes the ELSE condition and adds to cart like the
if (state.checkoutCart.includes(payload.item))
isn't being recognized or working at all.
https://i.imgur.com/LLB790Z.png
VueX devtools shows the same thing. An "item" object inside an object inside an array.
I also tried:
ADD_ITEM_TO_CART(state, payload) {
console.log("add_item_to_cart"); <---ONLY PART THAT SHOWS UP IN CONSOLE.LOG() WHEN EXECUTED
//LOOP THROUGH ALL ARRAY ENTRIES TO GAIN ACCESS TO state.checkoutCart.item
for (let i = 0; i < state.checkoutCart.length; i++) {
console.log("i=", i);
console.log("state.checkoutCart.item", state.checkoutCart.item);
//IF ITEM ALREADY IN checkoutCart, UPDATE IT'S QUANTITY
if (state.checkoutCart[i].item.includes(payload.item)) {
state.checkoutCart.quantity += payload.quantity;
console.log("Item already in cart");
return;
};
}
//IF ITEM NOT IN checkoutCart, UPDATE THE QUANTITY PROPERTY AND ADD ITEM TO CART
payload.item.quantity = payload.quantity;
state.checkoutCart.push(payload);
},
because I figured I needed to loop through all the array entries. BUT the for loop doesn't even run, and with this code nothing gets added to the cart at all.
I can't figure out what I'm doing wrong here. Can somebody help? Is my syntax wrong? Or is my logic? Am I accessing the arrays/objects incorrectly? How do I write the "ADD_ITEM_TO_CART" mutator function correctly? I've literally spent all day on this and my brain is shutting down.
EDIT:
https://i.imgur.com/bkU8YSo.png
PAYLOAD
<div v-for="item in items"> <--ACTUALLY PROP FROM PARENT COMPONENT BUT SAME IDEA
<p>
Qty
<select v-model="quantity">
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
</select>
</p>
<p>
<button type="button" #click="addToCart()">
Add to Cart
</button>
</p>
</div>
let quantity = ref("1");
const addToCart = () => {
console.log("addToCart Running");
store.dispatch("cartAdd", { item: item.value, quantity: quantity.value });
};
That is because your if condition is not checking for what you think.
Array.prototype.includes checks if a value is in the array but there are two cases:
the value is a primary type (string, number, boolean, ...). It compares by value.
the value is an object. Then it compares by reference.
So here, you are checking if the reference of your item object is already included in the array. But it's not, since it's a new object.
Solution: check if there is an object with the same values, not reference.
You can use the some method, and you have to write a condition that checks if two items are equals.
Here is an example if your items have an id:
if (state.checkoutCart.some(item => item.id === payload.item.id))
The problem is indeed inside ADD_ITEM_TO_CART mutation.
As Kapcash has pointed out, two objects having the same properties and the same values are not the same.
In other words, .includes() checks for identity, not equality. To better understand this, consider this example:
const a = { foo: 'bar' }
const b = [{ foo: 'bar' }]
const c = [a]
const d = [{ ...a }]
console.log(b.includes(a)) // false
console.log(c.includes(a)) // true
console.log(d.includes(a)) // false
To get past this, use Kapcash's answer.
I'll just mention the standard way of dealing with this problem is using unique identifiers on objects (e.g: uuids).
Once you fix the above, it's still not going to work, because you'll run into the following problem: inside the if you're attempting to alter state.checkoutCart's quantity. And an array does not have a quantity property.
The proper logic to achieve the desired functionality is (assuming the unique identifier on checkoutCart members is item._id, from the pictures you posted):
ADD_ITEM_TO_CART(state, payload) {
// find the index of a cart element having same `item._id` value as the payload
const index = state.checkoutCart.findIndex(
(el) => el.item._id === payload.item._id
)
if (index > -1) {
// if the index is higher than `-1`, an element was found
// create a copy, update its quantity and then
// replace the original element with the copy
const copy = { ...state.checkoutChart[index] }
copy.quantity += payload.quantity
state.checkoutCart.splice(index, 1, copy)
} else {
// no element found, add payload to `checkoutCart`
state.checkoutCart.push(payload)
}
}
Side note: None of your items should contain a quantity property. That's the cart's responsibility, not the item's. By adding it to the item you end up with two sources of truth which you'd need to keep in sync. You don't want this type of problem.

Sort array based on intermediate model's attribute

I have three models (I am using Vue/Vuex-ORM on the frontend). Category, CategoryItem and Item.
I'm able to fetch an array of categories, and within each category is an array of items. An intermediate join model defines the relationships of these two models, and I am able to access as such:
// array of categories, each category has array of items
const categories = Category.query().where('pack_id', this.selectedPack.id).with('items').get();
categories.map(category => {
category.items.forEach(item => {
console.log('item.pivot: ', item.pivot); // pivot refers to join model
// how to order items based on item.pivot?
})
})
Within the .forEach, I can access the join model with item.pivot. What I am looking to do however, is sort each category's items based on item.pivot.position.
I started going down a path where the first line inside of the .map I defined a new empty array, and would then theoretically push in a new value based on whether the position was higher or lower, but I couldn't quite wrap my head around how to accomplish this.
Thanks!
Well just my luck. A half hour after posting this question, I figure it out! Here was what I did, in case anyone is curious.
categories() {
const categories = Category.query().where('pack_id', this.selectedPack.id).with('items').get();
categories.forEach(category => category.items.sort(this.compare));
return cats;
}
compare(a, b) {
let comparison = 0;
if (a.pivot.position > b.pivot.position) comparison = 1;
else if (a.pivot.position < b.pivot.position) comparison = -1;
return comparison;
},

Updating components based on sorted array. Angular (4.3)

Working with an array of data that we want to be able to sort for display in a component, and it doesn't seem to be sorting or updating the DOM, however I have a working code sample that properly demonstrates the concept, and it should be sorting, but in the angular app, it's simply not getting sorted.
The parent component that houses the original data stores the data on an Input parameter object called Batch, and the array we're sorting is on Batch.Invoices.Results. The event from the child component is fine, and the appropriate data is confirmed to bubble to the parent component.
The function that's supposed to sort the array looks like this:
public OnInvoiceSortChange({orderValue, orderAscending}){
console.log(`Invoice Sorting has been called. Value: ${orderValue} . Ascending? ${orderAscending}`);
console.log(`Before:`);
console.log(this.BatchViewModel.Invoices.Results.map(x => x.VendorName));
const sortingArray = [...this.BatchViewModel.Invoices.Results];
if(orderAscending){
const sorted = sortingArray.sort((a, b) => a[orderValue] > b[orderValue] ? 1 : 0);
this.BatchViewModel.Invoices.Results = sorted;
console.log('Sorted');
console.log(sorted.map(x => x.VendorName));
} else {
const sorted = sortingArray.sort((a, b) => a[orderValue] < b[orderValue] ? 1 : 0);
this.BatchViewModel.Invoices.Results = sorted;
console.log(sorted.map(x => x.VendorName));
}
console.log(`After:`);
console.log(this.BatchViewModel.Invoices.Results.map(x => x.VendorName));
}
All the console logs are for debugger visibility, and the output is this:
Where in my testing file (non-angular) looks like this:(where data is a direct copy of the array from the Angular app.
const ascendingData = [...data];
const descendingData = [...data];
const sortedDescending = descendingData.sort((a, b) => a['VendorName'] < b['VendorName']? 0 : 1)
const sortedAscending = ascendingData.sort((a, b) => a['VendorName'] > b['VendorName']? 0 : 1);
const vendorListAscending = sortedAscending.map(x => x.VendorName);
const vendorListDescending = sortedDescending.map(x => x.VendorName);
console.log(vendorListDescending);
console.log(vendorListAscending);
and the output looks like this:
So I see that the sorting should work, but it's just not happening in Angular.
How can I get the array sorted, and update the DOM as well?
The function you pass to sort is wrong. It is supposed to return a negative value for "less", a positive value for "greater" or zero for "equal". If orderValue is numeric then it's easiest to just return a[orderValue] - b[orderValue], if not then just change your 0 to -1.
(By the way, name orderKey could be a bit clearer maybe?)
I don't think angular has anything to do here, but I cannot tell now why you get different results. Anyway, your sort function is invalid (it states that a equals b, but at the same time b is greater than a), I hope fixing this function helps.

How to use forEach to update all values matching this criteria

I am creating a function for selecting/deselecting checkboxes representing objects in my array.
Currently I have:
selectAll(allType: string, state) {
this.modalData.columnPermissions.forEach(a =>
a["can" + allType] = state
);
}
(allType allows me to target "canRead" or "canWrite" keys depending on which SELECT ALL the user chooses from the top of 2 columns.)
This is working fine - however a scenario has now been introduced where if an object contains the property IDM=TRUE then "canWrite" should always be FALSE
I'm struggling on how to now adapt my selectAll function to exclude any object with a property of IDM=TRUE on the KEY canWrite
Any help is appreciated
I have resolved this with info from depperm
this.modalData.columnPermissions.forEach(a => {
if (allType === 'Write' && !a.IDM) {
a["can" + allType] = state
} else if (allType === 'Read') {
a["can" + allType] = state
}
})

Trouble with Bootstraps typeahead in angular 5

I am having a difficult time getting Bootstraps typeahead in angular 5 working would appreciate some advice. The problem I have is that I don't know how to set the input field to equal the city + state for exaple "New york, NY" in bootstraps search method example. I am new to Typescript and the new fat arrow feature in JavaScript any help would be greatly appreciated.
model array of objects
public model: any;
example of data that I am getting
{
"city":"New York",
"latitude":40.7127837,
"longitude":-74.0059413,
"state":"New York",
"stateCode":"NY"
}
Search method here I am trying to set the location items to filter 'city,'+'state'
search = (text$: Observable<string>) => text$
.debounceTime(200)
.distinctUntilChanged()
.map(term => term.length < 2 ? [] : this.locationItems.filter(item => item.city.toLowerCase().indexOf(term.toLowerCase()) > -1).slice(0, 10));
The arrow function inside the last map in your chain needs to map (transform) the value of the string the user has typed into the search box to an array of items that will be presente to the user as suggestions.
You start nice, by askin if term is only one character long (or an empty string) and do nt even run the search, imediatelly returning empty array. For the other part, you need to find which items you want to present to the user.
This part depends on your business logic, but I assume that you want user to be searching by either state or stateCode? Anyway, this part is your business logic and you can change and improve it according to your busniess model. I'm giving a very simply function in the code below.
// util function
// returns true if the "term" can be used to match the "item" from your data
// change this according to your business logic
function matches(term: string, item: Item): boolean {
return item.state.toLowerCase().includes(term.toLowerCase())
|| item.stateCode.toLowerCase().includes(term.toLowerCase())
}
The lambda in the last map can be like this.
term => {
if (term.length < 2) {
return []
}
// arary of matching results
const matching = this.filter(item => matches(term, item))
// now we transform this to the format you specified
const formatted = matching.map(item => `${item.state}, ${item.stateCode}`)
return formatted
// you can also .slice(0, 10) here like you did in your example to keep the number of suggestions no more than 10
}

Categories

Resources