Updating object in array hook react js - javascript

I trying to update the quantity by pressing the "up" button.
This is my code.
const increaseHandler = (x) => {
let newCart = [...props.cartItems];
let exist = newCart.find((item) => item.id === x.id);
if (exist) {
exist.quantity++;
} else {
exist = {
...props.cartItems,
quantity: 1,
};
}
newCart.push(exist);
props.setCartItems(newCart);
};
Here is the button:
<button className="up" onClick={() => increaseHandler(items)}>
up
</button>
Every time I click on the "up" button, it appends a duplicate of the item on the cartItems
I increased the quantity but it appends the object to the array.

You can update by using map:
const increaseHandler = (x) => {
props.setCartItems((preState) =>
preState.map((item) => (item.id === x.id ? { ...item, quantity: item.quantity + 1 } : item)),
);
};

Even though you've shallow copied the cart items array you are still mutating the individual items with the post increment.
const increaseHandler = (x) => {
let newCart = [...props.cartItems];
let exist = newCart.find((item) => item.id === x.id);
if (exist) {
exist.quantity++; // <-- mutation!!
} else {
exist = {
...props.cartItems,
quantity: 1,
};
}
newCart.push(exist); // <-- adds duplicate items
props.setCartItems(newCart);
};
You still need to create a new item reference, shallow copying the properties and updating the quantity property. You should only push new elements into the cart array.
const increaseHandler = (x) => {
const newCart = [...props.cartItems];
let exist = newCart.find((item) => item.id === x.id);
if (exist) {
exist = {
...exist,
quantity: exist.quantity + 1;
};
} else {
exist = {
...props.cartItems,
quantity: 1,
};
newCart.push(exist);
}
props.setCartItems(newCart);
};
It's more common to map the previous state to the next state, and I suggest using a functional state update to ensure correctly updating from the previous state versus any state closed over in callback scope (possibly stale).
const increaseHandler = (x) => {
props.setCartItems(items => {
const inCart = items.some((item) => item.id === x.id);
if (inCart) {
return items.map((item) => item.id === x.id
? {
...item,
quantity: item.quantity + 1,
}
: item);
}
return items.concat({
...props.cartItems,
quantity: 1,
});
});
};

You're setting the entire cartItems array into the single exist object you've created. So you're adding the entire cart to exist and then setting exist.quantity to be 1, and pushing that whole thing into newCart and setting it into state.
Try spreading exist rather than props.cartItems in your else block.
Based on your images and nomenclature, is this the FreeCodeCamp/Wiebenfalk tutorial on react & TS? If so, I did the same tutorial, and here's my add to cart function:
const handleAddToCart = (clickedItem: CartItemType) => {
setCartItems((prevState) => {
// check if item is in cart by looking for id of clicked item in array of cartItems already in state
const isItemInCart = prevState.find((item) => item.id === clickedItem.id);
if (isItemInCart) {
// search for item id matching clicked item; increment that amount
return prevState.map((item) =>
item.id === clickedItem.id
? { ...item, amount: item.amount + 1 }
: item
);
}
return [...prevState, { ...clickedItem, amount: 1 }];
});
};

Related

redux action to remove a list of items from the shopping cart or update the item quantity

I want to remove a list of items from the shopping cart or update the quantity if the item is available but not in the requested quantities. Does it make sense to use only a redux action to achieve my goal or it is better to have 2 different actions to avoid some business logic in the reducer? Here's my code:
case cartListItemRemoved: {
const productIds = action.payload;
const newFilteredItems = state.items.filter(
item => !productIds.find(el => el.id === item.id && el.stockAmount === 0),
);
const updatedFilteredItems = newFilteredItems.map(item => {
const { quantity } = productIds.find(el => el.id === item.id);
if (quantity && quantity > 0) {
return {
...item,
quantity: quantity,
};
}
return item;
});
return {
...state,
items: updatedFilteredItems,
};
}

React state changing on change copy of original state?

I am trying to make a simple game like purple pairs in Purple palace game using React. I have a method named clickBtn, it is supposed to increment the count on click and decrement on second click but I don't know why the handleClick method is changing chosen and clicked property of state even if a copy of the new state is made without using setState method. Can you help me fix this?
class GameBoard extends React.Component {
constructor() {
super();
this.state = {
score: 0,
time: 0,
list: [...generateObList(), ...generateObList()],
count: 0
};
}
handleClick = (id) => {
this.clickBtn(id);
const list = this.state.list.slice();
const current = list.find((a) => a.id === id);
for (let x of list) {
if (x.clicked && x.value == list.find((a) => a.id == id).value) {
x.chosen = true;
x.clicked = false;
current.chosen = true;
current.clicked = false;
// this.setState((prev) => ({
// list: prev.list.map((el) =>
// el.id === id ? current : el.value === current.value ? x : el
// ),
// score: prev.score + 1
// }));
}
}
};
clickBtn = (id) => {
const current = this.state.list.slice().find((e) => e.id === id);
let deClick = current.clicked;
current.clicked = !current.clicked;
console.log(current);
this.setState(
(prev) => ({
list: prev.list.map((el) => (el.id === id ? current : el)),
count: prev.count + (deClick ? -1 : 1)
}),
() => {
console.log(this.state.count, deClick, current);
}
);
};
render() {
const boardStyle = {
gridTemplateColumns: `repeat(5, 1fr)`,
gridTemplateRows: `repeat(5,1r)`
};
let list = this.state.list.map((n) => (
<Card
value={n.value}
onClick={(e) => {
this.handleClick(n.id);
}}
show={!n.chosen}
/>
));
return (
<div class="gameBoard" style={boardStyle}>
{list}
</div>
);
}
}
slice() just return a shallow copy of the array so both original and copy refer to the same array items if the items are reference type ( objects ).
For object slice copies object references into the new array. Both the
original and new array refer to the same object. If a object changes,
the changes are visible to both the new and original arrays.
Try to deep copy the objects. You can easily do deep copy by using, JSON.parse(JSON.stringify(arr))
Also you can try lodash's cloneDeep() method.
For more details about deep cloning - https://stackoverflow.com/a/122704/11306028

How to set a counter for duplicate values in React?

My code is basically a form with a text input and a submit button. Each time the user input data, my code adds it to an array and shows it under the form.
It is working fine; however, when I add duplicate values, it still adds it to the list. I want my code to count these duplicates and show them next to each input.
For example, if I input two "Hello" and one "Hi" I want my result to be like this:
2 Hello
1 Hi
Here is my code
import React from 'react';
import ShoppingItem from './ShoppingItem';
class ShoppingList extends React.Component {
constructor (props){
super(props);
this.state ={
shoppingCart: [],
newItem :'',
counter: 0 };
}
handleChange =(e) =>
{
this.setState ({newItem: e.target.value });
}
handleSubmit = (e) =>
{
e.preventDefault();
let newList;
let myItem ={
name: this.state.newItem,
id:Date.now()
}
if(!this.state.shoppingCart.includes(myItem.name))
{
newList = this.state.shoppingCart.concat(myItem);
}
if (this.state.newItem !=='')
{
this.setState(
{
shoppingCart: newList
}
);
}
this.state.newItem ="" ;
}
the rest of my code is like this:
render(){
return(
<div className = "App">
<form onSubmit = {this.handleSubmit}>
<h6>Add New Item</h6>
<input type = "text" value = {this.state.newItem} onChange ={this.handleChange}/>
<button type = "submit">Add to Shopping list</button>
</form>
<ul>
{this.state.shoppingCart.map(item =>(
<ShoppingItem item={item} key={item.id} />
)
)}
</ul>
</div>
);
}
}
export default ShoppingList;
Issues
this.state.shoppingCart is an array of objects, so this.state.shoppingCart.includes(myItem.name) will always return false as it won't find a value that is a string.
this.state.newItem = ""; is a state mutation
Solution
Check the newItem state first, if empty then return early
Search this.state.shoppingCart for the index of the first matching item by name property
If found then you want to map the cart to a new array and then also copy the item into a new object reference and update the quantity.
If not found then copy the array and append a new object to the end with an initial quantity 1 property.
Update the shopping cart and newItem state.
Code
handleSubmit = (e) => {
e.preventDefault();
if (!this.state.newItem) return;
let newList;
const itemIndex = this.state.shoppingCart.findIndex(
(item) => item.name === this.state.newItem
);
if (itemIndex !== -1) {
newList = this.state.shoppingCart.map((item, index) =>
index === itemIndex
? {
...item,
quantity: item.quantity + 1
}
: item
);
} else {
newList = [
...this.state.shoppingCart,
{
name: this.state.newItem,
id: Date.now(),
quantity: 1
}
];
}
this.setState({
shoppingCart: newList,
newItem: ""
});
};
Note: Remember to use item.name and item.quantity in your ShoppingItem component.
Replace your "handleSubmit" with below one and check
handleSubmit = (e) => {
e.preventDefault();
const { shoppingCart, newItem } = this.state;
const isInCart = shoppingCart.some(({ itemName }) => itemName === newItem);
let updatedCart = [];
let numberOfSameItem = 1;
if (!isInCart && newItem) {
updatedCart = [
...shoppingCart,
{
name: `${numberOfSameItem} ${newItem}`,
id: Date.now(),
itemName: newItem,
counter: numberOfSameItem
}
];
} else if (isInCart && newItem) {
updatedCart = shoppingCart.map((item) => {
const { itemName, counter } = item;
if (itemName === newItem) {
numberOfSameItem = counter + 1;
return {
...item,
name: `${numberOfSameItem} ${itemName}`,
itemName,
counter: numberOfSameItem
};
}
return item;
});
}
this.setState({
shoppingCart: updatedCart,
newItem: ""
});
};

add selected multiple radio buttons value and uniqueId to an array

I am mapping multiple radio buttons (group) options and when the user click on radio buttons, would like to add selected values and uniqueIds to an array.
with my current code I can get the value that I am currently clicking on but can't add to array.
{result !== null && result.length > 0 ? (
<div>
{result.map(item => {
const label = item.question
const listOptions = item.options.map(item => {
return (
<Radio
name={item.uniqueId}
inline
value={item.uniqueId}
key={item.uniqueId}
className="radio-options"
checked={item.checked}
onChange={e => {
this.handleChange(label, uniqueId);
}}
>
{item.options}
</Radio>
);
});
return (
<div className="radio-options-overview">
<p>{label}</p>
{listOptions}
</div>
);
})}
handleChange = (label, uniqueId) => {
console.log(label, uniqueId);
let addToArray = [];
this.setState({ selectedItems: addToArray})
};
array would look something like this,
[
{ "label": "ageRange", "uniquId": 2 },
{ "label": "smoker", "uniquId": 1 },
{ "label": "exOption", "uniquId": 3 }
]
You are nearly there. #Clarity provided good solution.
if you wanting to replace exisiting value and replace it with new one
Try This
handleChange = (label, uniqueId) => {
const { selectedItems } = this.state
// Find items that already exists in array
const findExistingItem = selectedItems.find((item) => {
return item.uniqueId === uniqueId;
})
if(findExistingItem) {
// Remove found Item
selectedItems.splice(findExistingItem);
// set the state with new values/object
this.setState(state => ({
selectedItems: [...state.selectedItems, {
label, uniqueId
}]
}))
} else {
// if new Item is being added
this.setState(state => ({
selectedItems: [...state.selectedItems, {
label, uniqueId
}]
}))
}
};
You can do like this:
handleChange = (label, uniqueId) => {
this.setState(state => ({ selectedItems: [...state.selectedItems, uniqueId]}));
};
By using array spread and functional form of setState you make sure that you don't directly mutate the state and add the items to the latest state.
In case you'd want to add an object with a label: uniqueId pair, you could do like so:
handleChange = (label, uniqueId) => {
this.setState(state => ({
selectedItems: [...state.selectedItems, {
[label]: uniqueId
}]
}));
};
EDIT: If you want to overwrite the items with the same labels, the easiest would be to store them as an object and not an array, so the item with the same label would overwrite an existing one:
handleChange = (label, uniqueId) => {
this.setState(state => {
return { selectedItems: { ...state.selectedItems, [label]: uniqueId } };
});
};
Honestly, I don't understand, what are you trying to do, but if you need to add object (or something else) inside an array, you could use .push() method. For example:
let addToArray = [];
let test = {"label": "ageRange", "uniquId": 2};
addToArray.push(test);
console.log(addToArray); //[{label: "ageRange", uniquId: 2}]

Redux check array if object match update object

const initialState = {
cart: []
}
export default function(state = initialState, action){
switch(action.type){
case 'CART_ADDITEM':
return {
...state,
cart: [...state.cart, action.payload]
}
case 'CART_REMOVEITEM':
return {
...state,
cart: state.cart.filter(item => item !== action.payload)
}
break;
}
return state;
}
here's my add/remove to array functions
this is the object in the array.
id(pin): "001"
Name(pin): "Dab on them"
price(pin): 100
img(pin): "/static/media/001.ac043cfc.png"
rarity(pin): "rare"
size(pin): "S"
quantity(pin): 1
how to check when adding a new item to the array if object match and if it does update quantity + 1.
same thing goes for removing item from array if quantity > 1 then quantity -1
You can check if is already present in the array an object with the same id and in which quantity. In that case update the quantity, otherwise add the object to the array.
I would write few helper methods to keep everything clear and separate.
You can check for presence by quantity with something like this:
const quantityForItem = (list, newItem) => {
const item = list.find(item => item.id === newItem.id)
return item && item.quantity || 0
}
You can increase or decrease the quantity of an item remapping the list and alterating the interested item:
const updateQuantity = (list, newItem, variation) =>
list.map(item => {
if ( item.id !== newItem.id ) return item
return { ...item, quantity: item.quantity + variation }
})
You can add a new item just concating it to the previous list. Pay attention: concat, not push, because we need to keep it immutable.
const add = (list, newItem) =>
list.concat(newItem)
The removal of an item could be like the one you already have:
const remove = (list, itemToRemove) =>
list.filter(item => item.id !== itemToRemove.id)
Putting everything together:
case 'CART_ADDITEM': {
const { cart } = state
const newItem = action.payload
if ( quantityForItem(cart, newItem) !== 0 ) {
return { cart: updateQuantity(cart, newItem, +1) }
}
return { cart: add(cart, newItem) }
}
case 'CART_REMOVEITEM': {
const { cart } = state
const itemToRemove = action.payload
if ( quantityForItem(cart, itemToRemove) > 1 ) {
return { cart: updateQuantity(cart, itemToRemove, -1) }
}
return { cart: remove(cart, itemToRemove) }
}

Categories

Resources