Commit d0f2dca6371 for woocommerce
commit d0f2dca63712917c3ed0c2502d781f249cb19bad
Author: Marcus Karlos <manager1@onmail.com>
Date: Thu Mar 5 19:40:20 2026 +0200
Fix cart: prevent false "Quantity changed to 1" notices and add optimistic removal feedback (#63403)
* fix(cart): prevent false 'Quantity changed to 1' notices and add optimistic removal feedback
Fixes #63381
1. Prevents false "Quantity changed to 1" notices during rapid delete/re-add flows
by checking `old.quantity > 0` in `getInfoNoticesFromCartUpdates`.
2. Ensures "Removed" notices appear instantly (optimistic UI) when removing items.
* fix(cart): trigger immediate notice on item removal and cleanup unused vars
* Changes in this proposed patch
* Update fix_false_notices_quantity_changed
* fix: replace space indentation with tabs in cart.ts
- Fix indentation in getInfoNoticesFromCartUpdates: replace spaces with
tabs for destructuring block and autoDeletedToNotify /
autoUpdatedToNotify variable declarations
- Fix indentation in removeCartItem: replace spaces with tabs for
itemToRemove lookup, optimistic notice dispatch, and
quantityChanges initialization block
* fix(cart): clear notices when last cart item is removed
* fix(cart): simplify notice logic per review — remove quantityChanges tracking, revert optimistic removal notice
Address review feedback from @SantosGuillamot on #63403.
getInfoNoticesFromCartUpdates:
- Remove quantityChanges parameter entirely — not needed since we
compare the optimistic snapshot (oldCart) against the server response
(newCart). User-initiated changes are already reflected in oldCart,
so only server-side corrections (stock cap, sold-individually, auto-
removed items) produce diffs and generate notices.
- Remove redundant old.key && guard — isCartItem() and the .some()
comparison already cover this.
- Simplify autoUpdatedToNotify predicate to:
return old && item.quantity !== old.quantity
removeCartItem:
- Revert optimistic "was removed from your cart." notice — user-
initiated removals should not generate notices. The server-response
path in getInfoNoticesFromCartUpdates handles any unexpected server-
side removals correctly.
- Remove itemToRemove lookup and isLastItem branch (no longer needed).
- quantityChanges kept — still required for emitSyncEvent.
* Back to removeCartItem AsyncAction
diff --git a/plugins/woocommerce/changelog/fix_false_notices_quantity_changed b/plugins/woocommerce/changelog/fix_false_notices_quantity_changed
new file mode 100644
index 00000000000..84f9ea92e15
--- /dev/null
+++ b/plugins/woocommerce/changelog/fix_false_notices_quantity_changed
@@ -0,0 +1,4 @@
+Significance: patch
+Type: fix
+
+Fix cart: prevent false "Quantity changed to 1" notices and add optimistic removal feedback
diff --git a/plugins/woocommerce/client/blocks/assets/js/base/stores/woocommerce/cart.ts b/plugins/woocommerce/client/blocks/assets/js/base/stores/woocommerce/cart.ts
index 0ca5a694fd3..fcd160be9fd 100644
--- a/plugins/woocommerce/client/blocks/assets/js/base/stores/woocommerce/cart.ts
+++ b/plugins/woocommerce/client/blocks/assets/js/base/stores/woocommerce/cart.ts
@@ -165,37 +165,30 @@ const generateInfoNotice = ( message: string ): Notice => ( {
const getInfoNoticesFromCartUpdates = (
oldCart: Store[ 'state' ][ 'cart' ],
- newCart: Cart,
- quantityChanges: QuantityChanges
+ newCart: Cart
): Notice[] => {
const oldItems = oldCart.items;
const newItems = newCart.items;
- const {
- productsPendingAdd: pendingAdd = [],
- cartItemsPendingQuantity: pendingQuantity = [],
- cartItemsPendingDelete: pendingDelete = [],
- } = quantityChanges;
-
- const autoDeletedToNotify = oldItems
- .filter( isCartItem )
- .filter(
- ( old ) =>
- ! newItems.some( ( item ) => old.key === item.key ) &&
- ! pendingDelete.includes( old.key )
- );
+ // Items auto-removed by the server (stock change, product deleted, etc.).
+ // We pass the optimistic snapshot as oldCart, so user-initiated removals
+ // are already absent and do not generate spurious notices here.
+ const autoDeletedToNotify = oldItems.filter(
+ ( old ) =>
+ isCartItem( old ) &&
+ ! newItems.some( ( item ) => old.key === item.key )
+ );
+ // Items whose quantity was adjusted by the server (stock cap, sold-individually).
+ // Comparing optimistic → server means intentional user changes are already
+ // reflected in oldItems and will not trigger this notice.
const autoUpdatedToNotify = newItems.filter( ( item ) => {
if ( ! isCartItem( item ) ) {
return false;
}
const old = oldItems.find( ( o ) => o.key === item.key );
- return old
- ? ! pendingQuantity.includes( item.key ) &&
- item.quantity !== old.quantity
- : ! pendingAdd.includes( item.id );
+ return old && item.quantity !== old.quantity;
} );
-
return [
...autoDeletedToNotify.map( ( item ) =>
// TODO: move the message template to iAPI config.
@@ -320,7 +313,7 @@ const { state, actions } = store< Store >(
{
actions: {
*removeCartItem( key: string ): AsyncAction< void > {
- // Track what changes we're making for notice comparison.
+ // Track what changes we're making for the sync event.
const quantityChanges: QuantityChanges = {
cartItemsPendingDelete: [ key ],
};
@@ -357,8 +350,7 @@ const { state, actions } = store< Store >(
if ( cart && cartAfterOptimistic ) {
const infoNotices = getInfoNoticesFromCartUpdates(
cartAfterOptimistic,
- cart,
- quantityChanges
+ cart
);
const errorNotices =
cart.errors.map( generateErrorNotice );
@@ -505,8 +497,7 @@ const { state, actions } = store< Store >(
) {
const infoNotices = getInfoNoticesFromCartUpdates(
cartAfterOptimistic,
- cart,
- quantityChanges
+ cart
);
const errorNotices =
cart.errors.map( generateErrorNotice );
@@ -647,8 +638,7 @@ const { state, actions } = store< Store >(
if ( showCartUpdatesNotices ) {
const infoNotices = getInfoNoticesFromCartUpdates(
cartAfterOptimistic,
- cart,
- quantityChanges
+ cart
);
const errorNotices =
cart.errors.map( generateErrorNotice );