Redux check array if object match update object - javascript

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

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

Updating object in array hook react js

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

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

Redux increase quantity in a shopping cart

I am struggling with the function of my reducer to add an item and increase its quantity if already present in cart.
What my code does so far is adding another "quantity" with 1 instead of updating the quantity already present in my state.
Here's my code :
reducer :
import { ADD_TO_CART } from "../actions/types";
export default function(state = [], action) {
switch (action.type) {
case ADD_TO_CART:
if (state.findIndex(el => el.item.title === action.item.title) === -1) {
return [...state, { item: action.item, quantity: action.quantity + 1 }];
} else {
return [...state, { quantity: action.quantity + 1 }];
}
default:
return state;
}
}
action :
import { ADD_TO_CART } from "./types";
import axios from "axios";
export const addToCart = id => dispatch => {
axios
.get(`https://api.itbook.store/1.0/search/${id}`)
.then(items =>
items.map((item, quantity) =>
dispatch({
type: ADD_TO_CART,
item,
quantity
})
)
);
};
Thanks
You are finding the index (which is great) but not doing anything with it (which is not so great):
import { ADD_TO_CART } from "../actions/types";
export default function(state = [], action) {
switch (action.type) {
case ADD_TO_CART:
const index = state.findIndex(el => el.item.title === action.item.title);
if (index === -1) {
return [...state, { item: action.item, quantity: action.quantity + 1 }];
} else {
// Use map to create a new state object
return state.map((item, i) =>
index === i //Only modify the found index
? { ...item, quantity: item.quantity + action.quantity } //Add the required quantity to the current quantity (not too sure about this)
: item //Don't modify other items
);
}
default:
return state;
}
}
import { ADD_TO_CART } from "../actions/types";
export default function (state = [], action) {
switch (action.type) {
case ADD_TO_CART:
const index = state.findIndex(el => el.item.title === action.item.title);
if (index > -1) {
const newState = [...state];
newState[index] = { ...newState[index], quantity: action.quantity + 1 };
return newState;
} else {
return [...state, { ...action.item, quantity: action.quantity + 1 }];
}
default:
return state;
}
}

React-Redux Shopping Cart - Decrease/Increase Item Quantity

I am doing a project on the React-Redux shopping cart. I am currently trying to build out functionality to allow a user to update the quantity of items being added to the Shopping Cart. I've already been able to get "Remove from Cart" to work. I've been searching around for different ways of doing it, but it seems that all of the Shopping Cart tutorials stop at "Add to Cart"! So I've been trying to puzzle through it on my own, but found very few examples online. Can anyone point me in the right direction?
Here's the shopping cart tutorial originally posted on Github:
https://github.com/reactjs/redux/tree/master/examples/shopping-cart
Here's what I've been trying to figure out:
ProductItem.js
const ProductItem = ({product, onAddToCartClicked, onRemoveFromCartClicked, onIncreaseQuanityClicked, onDecreaseQuantityClicked }) => (
<div style={{ marginBottom: 20, marginLeft: 20}}>
<Card>
<CardBody>
<Product
title={product.title}
price={product.price}
inventory={product.inventory} />
<Button color="primary"
onClick={onAddToCartClicked}
disabled={product.inventory > 0 ? '' : 'disabled'}>
{product.inventory > 0 ? 'Add to cart' : 'Sold Out'}
</Button>
<Button color="success"
onClick={onIncreaseQuanityClicked}
disabled={product.inventory > 0 ? '' : 'disabled'}> +
</Button>
<Button color="danger"
onclick={onDecreaseQuantityClicked}
disabled={product.inventory > 0 ? '' : 'disabled'}> -
</Button>
<Button onClick={onRemoveFromCartClicked}>Remove</Button>
</CardBody>
</Card>
</div>
)
ProductsContainer.js
const ProductsContainer = ({ products, addToCart }) => (
<ProductsList title="Products">
{products.map(product =>
<ProductItem
key={product.id}
product={product}
onAddToCartClicked={() => addToCart(product.id)}
onIncreaseQuantityClicked={() => increaseQuantity(product.id)}
onDecreaseQuantityClicked={() => decreaseQuantity(product.id)} />
)}
</ProductsList>
)
ProductsContainer.propTypes = {
products: PropTypes.arrayOf(PropTypes.shape({
id: PropTypes.number.isRequired,
title: PropTypes.string.isRequired,
price: PropTypes.number.isRequired,
inventory: PropTypes.number.isRequired
})).isRequired,
addToCart: PropTypes.func.isRequired,
increaseQuantity: PropTypes.func.isRequired
}
const mapStateToProps = state => ({
products: getVisibleProducts(state.products)
})
export default connect(
mapStateToProps,
{ addToCart, increaseQuantity, decreaseQuantity }
)(ProductsContainer)
reducer/products.js
const products = (state, action) => {
switch (action.type) {
case ADD_TO_CART:
return {
...state,
inventory: state.inventory - 1
}
case REMOVE_FROM_CART:
return {
...state,
inventory: state.inventory + 1
}
case INCREASE_QUANTITY:
return {
...state,
//NOT SURE WHAT ELSE TO PUT HERE
}
case DECREASE_QUANTITY:
return {
...state,
NOT SURE WHAT ELSE TO PUT HERE EITHER
}
default:
return state
}
}
Can anyone point me in the right path? If I'm even on the right path at all, or suggest any tutorials or websites that could help?
First, I think you should include quantity in your state and separate the logic of quantity from inventory. Here's what your state tree can look like:
{
cart: [
{id: 1, quantity: 3},
{id: 3, quantity: 2}
],
products: [
{id: 1, inventory: 10, ...},
{id: 2, inventory: 10, ...},
{id: 3, inventory: 10, ...}
]
}
Cart stores the products added to the cart and products contains all of the available products.
With this state tree in mind, we can use the following action creators:
function quantityUp(id, val){
return {type: 'QTY_UP', id, up: val}
}
function quantityDown(id, val){
return {type: 'QTY_DOWN', id, down: val}
}
Now, we can create our reducers. Since we separated quantity from inventory, we should also separate the reducers to reflect this logic.
const cart = (state, action) => {
switch(action.type){
case 'QTY_UP':
return Object.assign([], state.map(item => {
if(item.id === action.id){
item.quantity += action.up;
}
return item;
));
case 'QTY_DOWN':
return Object.assign([], state.map(item => {
if(item.id === action.id){
item.quantity -= action.down;
}
return item;
));
default:
return state;
}
};
The following actions should also be part of your cart reducer: ADD_TO_CART, REMOVE_FROM_CART
The products reducer should take care of modifying the products themselves, if needed. One case would be to modify the inventory of an item when an item has been purchased.
Let's create the action creators first:
//cart will be an array
function purchase(cart){
return {type: 'PURCHASE', cart}
}
Now we can create the reducer:
const products = (state, action) => {
switch(action.type){
case 'PURCHASE':
const ids = action.cart.map(item => item.id);
return Object.assign([], state.map(item => {
if(ids.includes(item.id)){
item.inventory -= action.cart.filter(p => p.id === item.id)[0].quantity;
}
return item;
}));
case default:
return state;
}
};
Now we can add products to your cart, edit the quantities of each product in the cart, and update the inventory of each product in your state when a product has been purchased.
Be sure to have an initial state setup before your reducer like:
const initialState = {
inventory: 0,
quantity: 0
}
Then you link your reducer to the state that you just declared:
const products = (state = initialState, action) => {
if you want your action to increase your quantity in state, you proceed as with inventory:
quantity: state.quantity + 1
As a reminder, the state is first initiated through one or multiple reducers and you create a store in redux by using for example
const store = createStore(yourReducer)
or
const store = createStore(combineReducers(allYourReducers))
Your store will have the global state of your app made of the sum of all your reducer's initialStates.
Then you can access and play with the state by dispatching your actions
store.dispatch(yourAction)
If everything in your app is well connected, you should see your state updating as you want.
You may check this course from Andrew Mead : https://www.udemy.com/react-2nd-edition/learn/v4/overview
Given your current code, addToCart and increaseQuantity are the same thing.
You can either:
1) Reuse the addToCart function in your container
<ProductItem
key={product.id}
product={product}
onAddToCartClicked={() => addToCart(product.id)}
onIncreaseQuantityClicked={() => addToCart(product.id)}
onDecreaseQuantityClicked={() => decreaseQuantity(product.id)} />
2) Implement the same logic in your reducer
case INCREASE_QUANTITY:
return {
...state,
inventory: state.inventory - 1
}
when we work on shopping cart, we should have cartItems array inside our cart state and every time we click on "add to cart" button, that item will be pushed to that array and we will "map" that array in the component we wanna render cart items.
const INITIAL_STATE = {
//you could have more properties but i focus on cartItems
cartItems: []
};
to add item to the cart, we should be careful when we write our code. because first time adding an item to the cart is easy, but what if we add the same item multiple times to the cart. so we need to group items inside the cartItems array. for this we need to write an utility function.
//cart.utils.js
export const addItemToCart = (cartItems, cartItemToAdd) => {
//find(condition) finds the first item in the array based on the condition.
const existingCartItem = cartItems.find(item => item.id === cartItemToAdd.id);
if (existingCartItem) {
//in order for change detection to trigger we have to rerender
//otherwise our quantity property will not be updated
//map will return a new array
//we need to return new versions of our state so that our component know to re render
//here we update the quantity property
return cartItems.map(item =>
item.id === cartItemToAdd.id
? { ...cartItemToAdd, quantity: item.quantity + 1 }
: item
);
}
//when you first time add a new item, sine exixtingCartItem will be falsy, it will pass the first if block and will come here
//quantity property gets attached the first time around since this if block wont run when it is a new item.
//in the beginning cartItems array is empty. every time you add a new item to this array, it will add "quantity:1" to this item object.
return [...cartItems, { ...cartItemToAdd, quantity: 1 }];
};
in your reducer file
import { addItemToCart } from "./cart.utils";
case INCREASE_QUANTITY:
return {
...state,
cartItems: addItemToCart(state.cartItems, action.payload)
};
for removing item from the cart, we need to write another utility function.
cart.utils.js
export const removeItemFromCart = (cartItems, cartItemToRemove) => {
//check if item is already in the cartItems
const existingCartItem = cartItems.find(
item => item.id === cartItemToRemove.id
);
//if there is only 1, upon clicking, we should remove the item from the array
if (existingCartItem.quantity === 1) {
return cartItems.filter(item => item.id !== cartItemToRemove.id);
}
return cartItems.map(item =>
item.id === cartItemToRemove.id
? { ...item, quantity: item.quantity - 1 }
: item
);
};
in reducer/products.js
import { addItemToCart, removeItemFromCart } from "./cart.utils";
case DECREASE_QUANTITY:
return {
...state,
cartItems: removeItemFromCart(state.cartItems, action.payload)
}

Categories

Resources