Leia_Organa
Leia_Organa

Reputation: 2094

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?

Upvotes: 1

Views: 15827

Answers (4)

Yilmaz
Yilmaz

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

JJJ
JJJ

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

klugjo
klugjo

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

Genia
Genia

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

Related Questions