Learn how to modify existing Shopping Lists using various update actions, manage concurrent updates, and handle the transition from anonymous to registered users.
After completing this page, you should be able to:
- Explain optimistic concurrency control and apply it by providing the correct Shopping List
version
during updates. - Combine multiple update actions (for example,
addLineItem
,addTextLineItem
,changeLineItemQuantity
) into a single, efficient API request. - Apply the
setCustomer
action to correctly transition an anonymous Shopping List to a registered customer.
version
of the Shopping List. This enables optimistic concurrency control, preventing accidental overwrites if the list has been modified by another process since you last fetched it. If the version doesn't match, the API will return a 409 Conflict
error, indicating you need to re-fetch the list to get the latest state before retrying the update.id
, key
, or slug
) in your application's state (for example, local storage, session storage, or a state management library such as Redux or Zustand).Why persist?
- Performance: Allows direct fetching of the list by its identifier in subsequent user interactions, avoiding the need for repeated, potentially slower queries. Querying directly by
id
is typically the fastest method. - Updates: Makes updating the list much easier, as you already have the necessary
id
readily available.
Common update actions
While there are many actions available, some common ones for modifying list content and basic properties include:
addLineItem
: Adds a Product Variant (ShoppingListLineItem
) to the list.addTextLineItem
: Adds a custom text entry (TextLineItem
) to the list (for example, for notes or non-product items).removeLineItem
: Removes a Product Variant from the list (remove partially by specifying quantity, or entirely).removeTextLineItem
: Removes a text entry from the list.changeLineItemQuantity
: Changes the quantity of a Product Variant in the list.changeTextLineItemQuantity
: Changes the quantity of a text entry in the list.setKey
: Sets or updates the user-defined unique key of the shopping list.changeName
: Changes the display name of the shopping list.setCustomer
: Associates the list with a specific customer (see Anonymous to Logged-In scenario below).setCustomField
: Sets or updates a custom field value on the list. (Note: Using custom fields for advanced features like sharing will be covered separately).setDeleteDaysAfterLastModification
: Sets automatic deletion days. (Note: Lifecycle management will be covered separately).
Batching updates
For efficiency, especially when performing multiple modifications, it's highly recommended to combine several update actions into a single API request. This reduces network latency and simplifies state management.
JSON example: Batched update actions payload
This example shows multiple update actions combined into one request payload.
// Example payload showing multiple update actions batched together.
{
"version": {{shopping-list-version}}, // <-- Must be the current version
"actions": [
{
"action": "changeLineItemQuantity",
"lineItemId": "{{lineItemId-to-change}}",
"quantity": 8
},
{
"action": "addLineItem",
"productId": "{{product-id-to-add}}",
"variantId": 1,
"quantity": 2
},
{
"action": "removeLineItem",
"lineItemId": "{{lineItemId-to-remove}}",
"quantity": 2 // Remove 2 items; omit quantity to remove the entire line item
},
{
"action": "addTextLineItem",
"name": { "en": "Gift Wrap Note" },
"description": { "en": "Use blue ribbon please" },
"quantity": 1
},
{
"action": "setCustomer",
"customer": {
"typeId": "customer",
"id": "{{customer-id}}"
}
}
]
}
TypeScript example: Building the ShoppingListUpdate
object
This demonstrates constructing the update object using the Commercetools TypeScript SDK.
import {
ShoppingListUpdate,
ShoppingListUpdateAction,
ShoppingListChangeLineItemQuantityAction,
ShoppingListAddLineItemAction,
ShoppingListRemoveLineItemAction,
ShoppingListSetCustomerAction,
ShoppingListAddTextLineItemAction // Import added for TextLineItem
} from '@commercetools/platform-sdk';
// You MUST have the correct ID and current version of the shopping list
const shoppingListId = '0a6eaa69-c255-4439-9394-1ea8254a61f0'; // <-- Replace
const currentShoppingListVersion = 3; // <-- Replace with the actual current version you fetched!
// Replace these with the actual IDs/values relevant to your update
const lineItemIdToChangeQty = '15f7d82b-c98c-466a-b25f-42af5274a6f7'; // <-- Replace
const productIdToAdd = 'b19a63bb-6453-4202-8e45-551d6e660d13'; // <-- Replace
const lineItemIdToRemove = 'eab2cb2f-edaa-4638-91be-db76980096b9'; // <-- Replace
const customerIdToSet = 'eaf18adc-c61e-4f2c-9d04-b6b8ad51d998'; // <-- Replace
// --- Construct the Update Actions Array ---
// Map the JSON actions to the corresponding TypeScript SDK types
const updateActions: ShoppingListUpdateAction[] = [
// Action 1: Change Line Item Quantity
{
action: 'changeLineItemQuantity',
lineItemId: lineItemIdToChangeQty,
quantity: 8,
} as ShoppingListChangeLineItemQuantityAction, // Type assertion for clarity
// Action 2: Add Line Item
{
action: 'addLineItem',
productId: productIdToAdd,
variantId: 1, // Make sure variant 1 exists for this product
quantity: 2,
} as ShoppingListAddLineItemAction,
// Action 3: Remove Line Item (or reduce quantity)
// If quantity is specified, it reduces the quantity by that amount.
// If quantity is omitted, the entire line item is removed.
{
action: 'removeLineItem',
lineItemId: lineItemIdToRemove,
quantity: 2, // Remove quantity 2 from this line item
// Omit 'quantity' property to remove the entire line item regardless of its current quantity
} as ShoppingListRemoveLineItemAction,
// Action 4: Add Text Line Item (Added Example)
{
action: 'addTextLineItem',
name: { en: 'Gift Wrap Note' },
description: { en: 'Use blue ribbon please' },
quantity: 1
} as ShoppingListAddTextLineItemAction,
// Action 5: Set Customer
{
action: 'setCustomer',
customer: { // Provide the customer resource identifier
typeId: 'customer',
id: customerIdToSet,
// key: "customer-key" // Alternatively, use key
},
} as ShoppingListSetCustomerAction,
];
// --- Construct the Update Body ---
// This object contains the version and the array of actions
const shoppingListUpdateBody: ShoppingListUpdate = {
version: currentShoppingListVersion,
actions: updateActions,
};
TypeScript example: Executing the update request
This shows how to send the update request using the SDK and handle potential errors, including version conflicts.
// Example POST request using the TypeScript SDK to update a specific list by ID,
// including error handling for version conflicts (409).
// Assuming 'apiRoot' is your configured SDK client instance
// Assuming 'shoppingListId' and 'shoppingListUpdateBody' are defined as above
apiRoot
.shoppingLists()
.withId({ ID: shoppingListId }) // Use .withId() or .withKey()
.post({
/**
* The update payload containing the version and actions.
* @type {ShoppingListUpdate}
*/
body: shoppingListUpdateBody,
})
.execute()
.then((response) => {
// On success, response.body contains the updated ShoppingList object
console.log('Successfully updated shopping list:');
console.log(JSON.stringify(response.body, null, 2)); // Pretty print the updated list
// Persist the new version: response.body.version
})
.catch((error) => {
// Handle errors, especially version conflicts (HTTP 409)
console.error('Error updating shopping list:');
console.error(JSON.stringify(error, null, 2));
// Specifically check for version conflict errors
if (error.statusCode === 409) {
console.error(
'\nVersion Conflict Detected! The shopping list has been modified since you fetched its version.',
'Please re-fetch the shopping list to get the latest version and apply your changes again.',
);
// Implement logic to refetch the list, get the new version, and potentially re-apply updates
}
});
Key scenario: Anonymous to logged-in user (setCustomer
)
anonymousId
) creates a Shopping List and then logs in or registers. To maintain the list, use the setCustomer
update action (shown in the examples above) to associate their existing anonymous list(s) with their new customerId
. This ensures list continuity across sessions and devices.- Handling Single-List Constraints: If your application business logic limits users to only one active Shopping List, you need a strategy for when an anonymous list needs to be associated with a customer who might already have a list:
- Merge: Combine the items from the anonymous list into the customer’s existing list.
- Delete: Delete the anonymous list after potentially merging items or notifying the user.
- Prompt: Ask the user how they wish to handle the two lists (for example, choose one, merge items).