diff --git a/plugins/woocommerce-blocks/assets/js/base/hooks/index.js b/plugins/woocommerce-blocks/assets/js/base/hooks/index.js
index 878f82391d3..63c2e42b535 100644
--- a/plugins/woocommerce-blocks/assets/js/base/hooks/index.js
+++ b/plugins/woocommerce-blocks/assets/js/base/hooks/index.js
@@ -2,6 +2,7 @@ export * from './use-query-state';
export * from './use-shallow-equal';
export * from './use-store-cart';
export * from './use-store-cart-coupons';
+export * from './use-store-cart-item';
export * from './use-store-products';
export * from './use-collection';
export * from './use-collection-header';
diff --git a/plugins/woocommerce-blocks/assets/js/base/hooks/use-store-cart-item.js b/plugins/woocommerce-blocks/assets/js/base/hooks/use-store-cart-item.js
new file mode 100644
index 00000000000..3dedd929222
--- /dev/null
+++ b/plugins/woocommerce-blocks/assets/js/base/hooks/use-store-cart-item.js
@@ -0,0 +1,47 @@
+/** @typedef { import('@woocommerce/type-defs/hooks').StoreCartItems } StoreCartItems */
+
+/**
+ * External dependencies
+ */
+import { useSelect } from '@wordpress/data';
+import { CART_STORE_KEY as storeKey } from '@woocommerce/block-data';
+
+/**
+ * Internal dependencies
+ */
+import { useStoreCart } from './use-store-cart';
+
+/**
+ * This is a custom hook for loading the Store API /cart/ endpoint and
+ * actions for removing or changing item quantity.
+ * See also: https://github.com/woocommerce/woocommerce-gutenberg-products-block/tree/master/src/RestApi/StoreApi
+ *
+ * @param {string} cartItemKey Key for a cart item.
+ * @return {StoreCartItems} An object exposing data and actions relating to cart items.
+ */
+export const useStoreCartItem = ( cartItemKey ) => {
+ const { cartItems, cartIsLoading } = useStoreCart();
+ const cartItem = cartItems.filter( ( item ) => item.key === cartItemKey );
+
+ const results = useSelect(
+ ( select, { dispatch } ) => {
+ const store = select( storeKey );
+ const isPending = store.isItemQuantityPending( cartItemKey );
+ const { removeItemFromCart } = dispatch( storeKey );
+
+ return {
+ isPending,
+ removeItem: () => {
+ removeItemFromCart( cartItemKey );
+ },
+ };
+ },
+ [ cartItemKey ]
+ );
+
+ return {
+ isLoading: cartIsLoading,
+ cartItem,
+ ...results,
+ };
+};
diff --git a/plugins/woocommerce-blocks/assets/js/blocks/cart-checkout/cart/full-cart/cart-line-item-row.js b/plugins/woocommerce-blocks/assets/js/blocks/cart-checkout/cart/full-cart/cart-line-item-row.js
index e440661743f..28887b2960f 100644
--- a/plugins/woocommerce-blocks/assets/js/blocks/cart-checkout/cart/full-cart/cart-line-item-row.js
+++ b/plugins/woocommerce-blocks/assets/js/blocks/cart-checkout/cart/full-cart/cart-line-item-row.js
@@ -7,6 +7,7 @@ import PropTypes from 'prop-types';
import QuantitySelector from '@woocommerce/base-components/quantity-selector';
import FormattedMonetaryAmount from '@woocommerce/base-components/formatted-monetary-amount';
import { getCurrency, formatPrice } from '@woocommerce/base-utils';
+import { useStoreCartItem } from '@woocommerce/base-hooks';
import { Icon, trash } from '@woocommerce/icons';
/**
@@ -21,6 +22,7 @@ import ProductLowStockBadge from './product-low-stock-badge';
*/
const CartLineItemRow = ( { lineItem = {} } ) => {
const {
+ key = '',
name = '',
summary = '',
permalink = '',
@@ -36,6 +38,10 @@ const CartLineItemRow = ( { lineItem = {} } ) => {
const purchasePrice = parseInt( prices.price, 10 ) * lineQuantity;
const saleAmount = regularPrice - purchasePrice;
+ const { removeItem, isPending: itemQuantityDisabled } = useStoreCartItem(
+ key
+ );
+
return (
@@ -60,14 +66,23 @@ const CartLineItemRow = ( { lineItem = {} } ) => {
|
- |
@@ -100,6 +115,7 @@ const CartLineItemRow = ( { lineItem = {} } ) => {
CartLineItemRow.propTypes = {
lineItem: PropTypes.shape( {
+ key: PropTypes.string.isRequired,
name: PropTypes.string.isRequired,
summary: PropTypes.string.isRequired,
images: PropTypes.array.isRequired,
diff --git a/plugins/woocommerce-blocks/assets/js/data/cart/action-types.js b/plugins/woocommerce-blocks/assets/js/data/cart/action-types.js
index f448e01c676..8b5c7a749dd 100644
--- a/plugins/woocommerce-blocks/assets/js/data/cart/action-types.js
+++ b/plugins/woocommerce-blocks/assets/js/data/cart/action-types.js
@@ -4,4 +4,6 @@ export const ACTION_TYPES = {
REPLACE_ERRORS: 'REPLACE_ERRORS',
APPLYING_COUPON: 'APPLYING_COUPON',
REMOVING_COUPON: 'REMOVING_COUPON',
+ ITEM_QUANTITY_PENDING: 'ITEM_QUANTITY_PENDING',
+ RECEIVE_REMOVED_ITEM: 'RECEIVE_REMOVED_ITEM',
};
diff --git a/plugins/woocommerce-blocks/assets/js/data/cart/actions.js b/plugins/woocommerce-blocks/assets/js/data/cart/actions.js
index f1045b4ffc8..e970b41d1eb 100644
--- a/plugins/woocommerce-blocks/assets/js/data/cart/actions.js
+++ b/plugins/woocommerce-blocks/assets/js/data/cart/actions.js
@@ -125,3 +125,59 @@ export function* removeCoupon( couponCode ) {
yield receiveRemovingCoupon( '' );
}
+
+/**
+ * Returns an action object to indicate if the specified cart item
+ * is being updated; i.e. removing, or changing quantity.
+ *
+ * @param {string} cartItemKey Cart item being updated.
+ * @param {boolean} isQuantityPending Flag for update state; true if API request is pending.
+ * @return {Object} Object for action.
+ */
+export function itemQuantityPending( cartItemKey, isQuantityPending ) {
+ return {
+ type: types.ITEM_QUANTITY_PENDING,
+ cartItemKey,
+ isQuantityPending,
+ };
+}
+
+/**
+ * Returns an action object to remove a cart item from the store.
+ *
+ * @param {string} cartItemKey Cart item to remove.
+ * @return {Object} Object for action.
+ */
+export function receiveRemovedItem( cartItemKey ) {
+ return {
+ type: types.RECEIVE_REMOVED_ITEM,
+ cartItemKey,
+ };
+}
+
+/**
+ * Removes specified item from the cart:
+ * - Calls API to remove item.
+ * - If successful, yields action to remove item from store.
+ * - If error, yields action to store error.
+ * - Sets cart item as pending while API request is in progress.
+ *
+ * @param {string} cartItemKey Cart item being updated.
+ */
+export function* removeItemFromCart( cartItemKey ) {
+ yield itemQuantityPending( cartItemKey, true );
+
+ try {
+ yield apiFetch( {
+ path: `/wc/store/cart/items/${ cartItemKey }`,
+ method: 'DELETE',
+ cache: 'no-store',
+ } );
+
+ yield receiveRemovedItem( cartItemKey );
+ } catch ( error ) {
+ yield receiveError( error );
+ }
+
+ yield itemQuantityPending( cartItemKey, false );
+}
diff --git a/plugins/woocommerce-blocks/assets/js/data/cart/reducers.js b/plugins/woocommerce-blocks/assets/js/data/cart/reducers.js
index 159b73d57d2..3ac6a3f3e55 100644
--- a/plugins/woocommerce-blocks/assets/js/data/cart/reducers.js
+++ b/plugins/woocommerce-blocks/assets/js/data/cart/reducers.js
@@ -3,6 +3,22 @@
*/
import { ACTION_TYPES as types } from './action-types';
+/**
+ * Sub-reducer for cart items array.
+ *
+ * @param {Array} state cartData.items state slice.
+ * @param {Object} action Action object.
+ */
+const cartItemsReducer = ( state = [], action ) => {
+ switch ( action.type ) {
+ case types.RECEIVE_REMOVED_ITEM:
+ return state.filter( ( cartItem ) => {
+ return cartItem.key !== action.cartItemKey;
+ } );
+ }
+ return state;
+};
+
/**
* Reducer for receiving items related to the cart.
*
@@ -13,6 +29,7 @@ import { ACTION_TYPES as types } from './action-types';
*/
const reducer = (
state = {
+ cartItemsQuantityPending: [],
cartData: {
coupons: [],
items: [],
@@ -64,6 +81,32 @@ const reducer = (
},
};
break;
+
+ case types.ITEM_QUANTITY_PENDING:
+ // Remove key by default - handles isQuantityPending==false
+ // and prevents duplicates when isQuantityPending===true.
+ const newPendingKeys = state.cartItemsQuantityPending.filter(
+ ( key ) => key !== action.cartItemKey
+ );
+ if ( action.isQuantityPending ) {
+ newPendingKeys.push( action.cartItemKey );
+ }
+ state = {
+ ...state,
+ cartItemsQuantityPending: newPendingKeys,
+ };
+ break;
+
+ // Delegate to cartItemsReducer.
+ case types.RECEIVE_REMOVED_ITEM:
+ state = {
+ ...state,
+ cartData: {
+ ...state.cartData,
+ items: cartItemsReducer( state.cartData.items, action ),
+ },
+ };
+ break;
}
return state;
};
diff --git a/plugins/woocommerce-blocks/assets/js/data/cart/selectors.js b/plugins/woocommerce-blocks/assets/js/data/cart/selectors.js
index dce180b9fcd..e904880d9e7 100644
--- a/plugins/woocommerce-blocks/assets/js/data/cart/selectors.js
+++ b/plugins/woocommerce-blocks/assets/js/data/cart/selectors.js
@@ -106,3 +106,27 @@ export const isRemovingCoupon = ( state ) => {
export const getCouponBeingRemoved = ( state ) => {
return state.metaData.removingCoupon || '';
};
+
+/**
+ * Returns cart item matching specified key.
+ *
+ * @param {Object} state The current state.
+ * @param {string} cartItemKey Key for a cart item.
+ * @return {Object} Cart item object, or undefined if not found.
+ */
+export const getCartItem = ( state, cartItemKey ) => {
+ return state.cartData.items.find(
+ ( cartItem ) => cartItem.key === cartItemKey
+ );
+};
+
+/**
+ * Returns true if the quantity is being updated for the specified cart item.
+ *
+ * @param {Object} state The current state.
+ * @param {string} cartItemKey Key for a cart item.
+ * @return {boolean} True if a item has a pending request to delete / update quantity.
+ */
+export const isItemQuantityPending = ( state, cartItemKey ) => {
+ return state.cartItemsQuantityPending.includes( cartItemKey );
+};
diff --git a/plugins/woocommerce-blocks/assets/js/type-defs/hooks.js b/plugins/woocommerce-blocks/assets/js/type-defs/hooks.js
index 3e42b427d0a..28ce7c673d0 100644
--- a/plugins/woocommerce-blocks/assets/js/type-defs/hooks.js
+++ b/plugins/woocommerce-blocks/assets/js/type-defs/hooks.js
@@ -23,4 +23,15 @@
* @property {boolean} isRemovingCoupon True when a coupon is being removed.
*/
-export {};
+/**
+ * @typedef {Object} StoreCartItems
+ *
+ * @property {boolean} isLoading True when cart items are being loaded.
+ * @property {Array} cartItems An array of items in the cart.
+ * @property {Function} isItemQuantityPending Callback for determining if a cart item
+ * is currently updating (i.e. remoe / change
+ * quantity).
+ * @property {Function} removeItemFromCart Callback for removing a cart item.
+ */
+
+ export {};