In React/Redux, how to calculate a Total price for a shopping cart

11,317

EDIT: More complete example of state/actions/reducers.

Do you actually need to store the totals in redux? Generally you want to keep the minimal state in redux, and calculate any derived data that you can in a selector. Subtotals and totals definitely fall into this category (unless you have a really unusual set your own price set up or something), so instead of storing them in the store, you can calculate them as needed, for example as part of your mapStateToProps function (assuming you're using react-redux).

Here's an example of what your state could look like. It includes two main slices, one for the catalog of items, and a second one specifically for the card.

{
  itemDetails: {
    item01: { name: 'Item One', price: 9.95 },
    item02: { name: 'Item Two', price: 10 },
    item03: { name: 'Item not appearing in this example', price: 50 },
  },
  cart: {
    item01: 1,
    item02: 2,
  },
}

Looking at the cart slice, all the reducer for that needs to do is manage the quantity in the cart, which you can do with basic actions. The actions and reducer may look something like (none of this is tested, is just to provide a feel for how this may look):

// Cart actions
const incrementQuantityInCart = (itemId) => ({
  type: 'incrementQuantityInCart',
  itemId,
})

const decrementQuantityInCart = (itemId) => ({
  type: 'decrementQuantityInCart',
  itemId,
})

const removeItemFromCart = (itemId) => ({
  type: 'removeItemFromCart',
  itemId,
})

// Cart reducer, would be combined with a separate reducer using redux's `combineReducers`
const cart = (state = {}, action) => {
  switch (action.type) {
    case 'incrementQuantityInCart':
      const currentQuantity = state[action.itemId] || 0
      return {
        ...state,
        [action.itemId]: currentQuantity + 1,
      }
    case 'decrementQuantityInCart':
      const currentQuantity = state[action.itemId] || 0
      return {
        ...state,
        [action.itemId]: Math.max(currentQuantity - 1, 0),
      }
    case 'removeItemFromCart':
      return {
        ...state,
        [action.itemId]: 0,
      }
    default:return state
  }
}

You could then have a selector such as:

function getCartContents(state) {
  const itemsInCart = Object.keys(state.cart)
    .filter(itemId => state.cart[itemId] > 0)
    .map(itemId => {
      const quantity = state.cart[itemId]
      const itemDetail = state.itemDetails[itemId]

      return {
        name: itemDetail.name,
        price: itemDetail.price,
        quantity,
        subtotal: itemDetail.price * quantity,
      }
    })

  const total = itemsInCart.reduce((total, item) => total + item.subtotal)

  return { itemsInCart, total }
}

// example output based on the above state
// {
//   itemsInCart: [
//     {
//       name: 'Item One',
//       price: 9.95,
//       quantity: 1,
//       subtotal: 9.95,
//     },
//     {
//       name: 'Item Two',
//       price: 10,
//       quantity: 2,
//       subtotal: 20,
//     },
//   ],
//   total: 29.95,
// }

You can then use this function either in or as your mapStateToProps for whatever component you want and it will have access to this data in it's props, so you can use as required.

Share:
11,317

Related videos on Youtube

Shaoz
Author by

Shaoz

Updated on June 04, 2022

Comments

  • Shaoz
    Shaoz almost 2 years

    I've searched for solutions on Google and SO but still cannot find an answer. They all stop at "Add item to cart" or "increase/decrease quantity" but never on calculating the Total, which is annoying!

    In my app, there's a list of items where the user can enter the quantity if an item, which updates the its price. All I want to know is how do you sum up all the prices of all items in a cart into a Total price, with Redux and display it in my React app?

    Also, if you can point me to any good shopping cart tutorial that actually goes beyond listing products in a cart, I'll be glad.

    Action:

    /**
     * @description UPDATE SINGLE ITEM PRICE WHEN USER ENTER QUANTITY
     *
     * @param{number} price
     * @param{number} quantity - enter by user from input
     */
    export const updateItemPrice = (price, quantity) => dispatch => {
      const result = price * quantity;
    
      return dispatch({
        type: UPDATE_ITEM_PRICE,
        subtotal: result
      });
    };
    

    Reducer:

    const INITIAL_STATE = {
      total: 0,
      subtotal: 0
    };
    
    const productsReducer = (state = INITIAL_STATE, action) => {
      switch (action.type) {
        // Update single item price
        case Types.UPDATE_ITEM_PRICE:
          return {
            ...state,
            subtotal: action.subtotal,
            total: // --> Here is where I'm stuck!!
          };
    
        default:
          return state;
      }
    };
    
    • emix
      emix over 5 years
      The store should not compute anything, it isn't its responsibility. Instead provide the total via the action's state.
    • simbathesailor
      simbathesailor over 5 years
      Idea is , whenever you are adding or dleting items form the cart you shoud update the totalPrice. Keep totalPrice as zero in initialState. In reducer whenever you are deleting the item decrease the price and same for adding an item
  • Shaoz
    Shaoz over 5 years
    Thanks for your response. Yes I'm using react-redux for the project but I'm a beginner with redux. Also I've tried to post a more comprehensive question but (at least twice now...) but got told-off because they said it was too long, so it's either too long or to short. What to do :|. Anyways I'll try your solution and see it works. Also I think I'll have to modify the structure of the cart object, thanks for showing me.
  • zkcro
    zkcro over 5 years
    The example I included above isn't an ideal solution, it was more intended to give you an idea of how this could work. (For example, in a real app you'd be more likely to have the list of items in a separate slice of your state, and just have the item id and quantity in your cart slice.) It possibly sounds a bit like you might need to adjust your thinking about what goes in the store a little, but as I said you generally want to be keeping the minimal state there, and then calculcating anything that can be derived from that. Will try and update with more complete reducer/actions if I can.