Reputation: 2094
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?
Upvotes: 1
Views: 15827
Reputation: 49182
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)
}
Upvotes: 0
Reputation: 3332
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.
Upvotes: 5
Reputation: 20865
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
}
Upvotes: 1
Reputation: 80
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
Upvotes: 1