React-Redux Shopping Cart - Decrease/Increase Item Quantity - javascript

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

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 product quantity in Redux shopping cart

I'm asking for help as I'm new to react and javascript. There are similar solutions to my problem but I still can't get it working.
I'm trying to create a redux store with an option to be able to update quantity in the shopping cart.
This is my store
const cartSlice = createSlice({
name: "cart",
initialState: {
products: [],
quantity: 0,
},
reducers: {
addProduct: (state, { payload }) => {
const product = state.products.find(
(product) => product.id === payload.id
);
if (product) {
state = state.products.map((product) =>
product.id === payload.id
? {
...product,
quantity: (product.quantity += payload.quantity),
}
: product
);
} else {
state.products.push(payload);
state.quantity += 1;
}
},
incQuantity: (state, { payload }) => {
const product = state.products.find((product) => product.id === payload);
product.quantity++;
},
decQuantity: (state, { payload }) => {
const product = state.products.find((product) => product.id === payload);
if (product.quantity === 1) {
const index = state.products.findIndex(
(product) => product.id === payload
);
state.products.splice(index, 1);
} else {
product.quantity--;
}
},
removeProduct: (state, { payload }) => {
const index = state.products.findIndex(
(product) => product.id === payload
);
state.products.splice(index, 1);
},
},
});
export const { addProduct, incQuantity, decQuantity, removeProduct } =
cartSlice.actions;
export default cartSlice.reducer;
This is how I update quantity on the product page where you can add a product to the cart
const handleQuantity = (type) => {
if (type === "dec") {
quantity > 1 && setQuantity(quantity - 1);
} else {
setQuantity(quantity + 1);
}
};
<Remove onClick={() => handleQuantity("dec")} />
<span className="product-detail-amount">{quantity}</span>
<Add onClick={() => handleQuantity("inc")} />
<button className="product-detail-button"
onClick={() => dispatch(addProduct({ ...product, quantity }))}>
Add to Cart </button>
<Remove
onClick={() => dispatch(decQuantity(product.id))}/>
<span className="product-detail-amount">
{product.quantity}</span>
<Add
onClick={() => dispatch(incQuantity(product.id))}/>
What it does now it keeps adding quantity to the same product without displaying a new one, same issues with updating the quantity (it changes the quantity only for the first product and when it's gone it starts updating another one)
Your help is much appreciated!
I think the problem is in the incQuantity and decQuantity reducers where you comparing product id to whole payload.
Should't have been payload.id?
Like this
incQuantity: (state, { payload }) => {
const product = state.products.find((product) => product.id === payload.id);
product.quantity++;
},
I don't believe that product.quantity++ is updating the state, it's just updating the local variable inside the reducer.
I'm wing it pretty hard , but will this work?
incQuantity: (state, { payload }) => {
state.products[state.products.findIndex(el => el.id === payload)].quantity = payload;
},
Edit:
Got a little lost. I believe you want it to increment by 1: ++
state.products[state.products.findIndex(el => el.id === payload)].quantity++
I got what was the issue, the original code works, should've used _id instead of id (I'm pulling data from mongodb where id has an underscore, I think this caused the problem). Thanks everyone who replied!

Create an array of different objects dynamicly

I have a list of users and near each user is a checkbox and a input number.
I want to save all data in the process of changing the data from input and checkbox:
import React, { useState } from "react";
import "./styles.css";
import { InputNumber, Checkbox } from "antd";
export default function App() {
const [user, setUser] = useState([
{
id: "",
nr: "",
checked: false
}
]);
function onChangeNr(id, value) {
console.log("changed", value, id);
setUser([
...user,
{
id: id,
nr: value,
checked: false
}
]);
}
console.log(user);
function onChangeCheck(e, id) {
console.log(`checked = ${e.target.checked}, ${id}`);
setUser([
...user,
{
id: id,
nr: "",
checked: e.target.checked
}
]);
}
return (
<div className="App">
<ul>
{[0, 1, 2, 3, 4].map((i) => {
return (
<li>
<p>User {i}</p>
<InputNumber
min={1}
max={10}
onChange={(id) => onChangeNr(i, id)}
/>
<Checkbox onChange={(check) => onChangeCheck(check, i)}>
Checkbox
</Checkbox>
</li>
);
})}
</ul>
</div>
);
}
Now i dont get the disserved result, but i want to get an array with all objects (unique objects). So each time when user change a data, the last value of the same id should override the previous value and at the final to get something like:
[
{id: 1, nr: 1, checked: true},
{id: 4, nr: 33, checked: true}
]
How to get the above result?
demo: https://codesandbox.io/s/elastic-nobel-79is1?file=/src/App.js:0-1165
To override the old state you can't use spread with an array => That will just combine the two arrays into one.
You either need to implement a search that will loop over all of the objects that you have inside the array and find the one that you need to update :
// loop over all entries
for(const i = 0; i < array.length; i++){
// is not the correct id, so not the correct obejct
if(array[i].id !== id) continue;
// found the correct object
array[i] = {id: i, ...};
}
// update state
setState(array);
But that would make js search every element once someone clicks the up / down arrows, so I would recommend that you put the state of the inputs into the array by their indexies:
// we already know the location of the state for the input because we put it into the same index as the key of the input!
array[i] = {id: i, ...}
setState(array)
Expample CodeSandbox
const reducer = (state, { type, i, checked, number }) => {
switch (type) {
case "setValues":
// reducer needs a new copy of the state
const newState = [...state];
newState[i] = {
id: i,
checked: checked ? checked : state[i]?.checked,
number: number ? number : state[i]?.number
};
console.log(newState);
return newState;
default:
break;
}
};
const [state, dispatch] = useReducer(reducer, initState);
return(
<InputNumber
min={1}
max={10}
onChange={(number) => {
dispatch({ type: "setValues", i, number });
}}
/>)

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

Redux state single item update showing previous version

My update reducer for my items (icdCode) in my array (icdCodes) is not updating properly within the react component (until I reload the entire component). First it was giving the duplicate key issue because the newly updated item in the array was showing up along with the previous state item within my list component after the action was triggered. I figured a workaround for that with some tweaks, but no matter what else I've tried, I can't update this item properly on the front-end.
Initial state:
state = {icdCodes: []}
The update reducer:
case UPDATE_ICD_CODE:
return {
...state,
icdCodes: [...state.icdCodes, action.payload]
}
Here's an excerpt from my react component loading the list of these array items (via mapping):
render() {
const { icdCodes } = this.props.icdCode;
return (
<Card body>
<ListGroup flush>
<Row>
this.icdCodes.map(({ _id, icdCode, icdCodeValue }) => (
<div>{icdCodeValue}</div>
)
</Row>
</ListGroup>
</Card>
);
}
}
IcdCodeItem.propTypes = {
getIcdCodes: PropTypes.func.isRequired,
icdCode: PropTypes.object.isRequired
};
const mapStateToProps = state => ({
icdCode: state.icdCode
});
export default connect(
mapStateToProps,
{ getIcdCodes, deleteIcdCode, updateIcdCode }
)(IcdCodeItem);
Here is what the action.payload returns (the updated icdCode item with a new value in place of the "icdCode" section):
{icdCodeVersion: "10",
_id: "5b922fbae1c4241b54ea8aa4",
icdCode: "I9",
icdCodeValue: "jam jam",
date: "2018-09-07T07:58:50.104Z", …}
The following code only partly solves the issue (allows me to edit only the first key (not the icdCode item, but the icdCode within the item - apologies for the horrible syntax) of my object rather than the whole object):
return {
...state,
icdCodes: state.icdCodes.map(
icdCode =>
icdCode._id === action.payload._id
? { ...icdCode, icdCode: action.payload.icdCode }
: icdCode
)
};
You can map icdCodes array, then if the element is right (here I'm checking by _id) then you can change it without mutating.
case UPDATE_ICD_CODE: {
const icdCodes = state.icdCodes.map(icd => {
if (icd._id === action.payload._id) {
return { ...icd, icdCode: action.payload.icdCode };
}
return icd;
});
return { ...state, icdCodes };
}
** Update after comments **
If you need to change more than one property here it is:
case UPDATE_ICD_CODE: {
const { _id, icdCode, icdCodeValue } = action.payload;
const icdCodes = state.icdCodes.map(icd => {
if (icd._id === _id) {
return { ...icd, icdCode, icdCodeValue };
}
return icd;
});
return { ...state, icdCodes };
}
If you want to change the object totally, it is easier:
case UPDATE_ICD_CODE: {
const { _id } = action.payload;
const icdCodes = state.icdCodes.map(icd =>
icd._id === _id ? action.payload : icd
)
return { ...state, icdCodes };
}

Categories

Resources