Skip to content

Commit 0516581

Browse files
authored
Merge pull request #3406 from Shopify/2025-10-deliveryAddressReplace
feat(cart): add cartDeliveryAddressesReplace mutation support
2 parents ab666dc + 13a6f89 commit 0516581

9 files changed

+272
-0
lines changed
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
---
2+
'@shopify/hydrogen': minor
3+
---
4+
5+
Add `cartDeliveryAddressesReplaceDefault` to handle the new `cartDeliveryAddressesReplace` Storefront API mutation (2025-10)
6+
7+
This new mutation replaces all delivery addresses on a cart in a single operation.
8+
9+
**Usage via cart handler:**
10+
```typescript
11+
const result = await cart.replaceDeliveryAddresses([
12+
{
13+
address: {
14+
deliveryAddress: {
15+
address1: '123 Main St',
16+
city: 'Anytown',
17+
countryCode: 'US'
18+
}
19+
},
20+
selected: true
21+
}
22+
]);
23+
```
24+
25+
**Usage via CartForm:**
26+
```tsx
27+
<CartForm action={CartForm.ACTIONS.DeliveryAddressesReplace}>
28+
{/* form inputs */}
29+
</CartForm>
30+
```

packages/hydrogen/src/cart/CartForm.test.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ describe('<CartForm />', () => {
8585
DeliveryAddressesAdd: 'DeliveryAddressesAdd',
8686
DeliveryAddressesUpdate: 'DeliveryAddressesUpdate',
8787
DeliveryAddressesRemove: 'DeliveryAddressesRemove',
88+
DeliveryAddressesReplace: 'DeliveryAddressesReplace',
8889
DiscountCodesUpdate: 'DiscountCodesUpdate',
8990
GiftCardCodesUpdate: 'GiftCardCodesUpdate',
9091
GiftCardCodesAdd: 'GiftCardCodesAdd',

packages/hydrogen/src/cart/CartForm.tsx

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -259,6 +259,20 @@ type CartDeliveryAddressesUpdateRequire = {
259259
} & OtherFormData;
260260
};
261261

262+
type CartDeliveryAddressesReplaceProps = {
263+
action: 'DeliveryAddressesReplace';
264+
inputs?: {
265+
addresses: Array<CartSelectableAddressInput>;
266+
} & OtherFormData;
267+
};
268+
269+
type CartDeliveryAddressesReplaceRequire = {
270+
action: 'DeliveryAddressesReplace';
271+
inputs: {
272+
addresses: Array<CartSelectableAddressInput>;
273+
} & OtherFormData;
274+
};
275+
262276
type CartCustomProps = {
263277
action: `Custom${string}`;
264278
inputs?: Record<string, unknown>;
@@ -304,6 +318,7 @@ type CartActionInputProps =
304318
| CartDeliveryAddressesAddProps
305319
| CartDeliveryAddressesRemoveProps
306320
| CartDeliveryAddressesUpdateProps
321+
| CartDeliveryAddressesReplaceProps
307322
| CartCustomProps;
308323

309324
export type CartActionInput =
@@ -324,6 +339,7 @@ export type CartActionInput =
324339
| CartDeliveryAddressesAddRequire
325340
| CartDeliveryAddressesRemoveRequire
326341
| CartDeliveryAddressesUpdateRequire
342+
| CartDeliveryAddressesReplaceRequire
327343
| CartCustomRequire;
328344

329345
type CartFormProps = CartActionInputProps & CartFormCommonProps;
@@ -373,6 +389,7 @@ CartForm.ACTIONS = {
373389
DeliveryAddressesAdd: 'DeliveryAddressesAdd',
374390
DeliveryAddressesUpdate: 'DeliveryAddressesUpdate',
375391
DeliveryAddressesRemove: 'DeliveryAddressesRemove',
392+
DeliveryAddressesReplace: 'DeliveryAddressesReplace',
376393
} as const;
377394

378395
function getFormInput(formData: FormData): CartActionInput {

packages/hydrogen/src/cart/createCartHandler.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ describe('createCartHandler', () => {
5858
expect(cart).toHaveProperty('addDeliveryAddresses');
5959
expect(cart).toHaveProperty('removeDeliveryAddresses');
6060
expect(cart).toHaveProperty('updateDeliveryAddresses');
61+
expect(cart).toHaveProperty('replaceDeliveryAddresses');
6162
});
6263

6364
it('can add custom methods', () => {

packages/hydrogen/src/cart/createCartHandler.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,10 @@ import {
6969
type CartDeliveryAddressesUpdateFunction,
7070
cartDeliveryAddressesUpdateDefault,
7171
} from './queries/cartDeliveryAddressesUpdateDefault';
72+
import {
73+
type CartDeliveryAddressesReplaceFunction,
74+
cartDeliveryAddressesReplaceDefault,
75+
} from './queries/cartDeliveryAddressesReplaceDefault';
7276
import type {CartBuyerIdentityInput} from '@shopify/hydrogen-react/storefront-api-types';
7377

7478
export type CartHandlerOptions = {
@@ -185,6 +189,32 @@ export type HydrogenCart = {
185189
updateDeliveryAddresses: ReturnType<
186190
typeof cartDeliveryAddressesUpdateDefault
187191
>;
192+
/**
193+
* Replaces all delivery addresses on the cart.
194+
*
195+
* This function sends a mutation to the storefront API to replace all delivery addresses on the cart
196+
* with the provided addresses. It returns the result of the mutation, including any errors that occurred.
197+
*
198+
* @param {CartQueryOptions} options - The options for the cart query, including the storefront API client and cart fragment.
199+
* @returns {CartDeliveryAddressesReplaceFunction} - A function that takes an array of addresses and optional parameters, and returns the result of the API call.
200+
*
201+
* @example
202+
* const result = await cart.replaceDeliveryAddresses([
203+
* {
204+
* address: {
205+
* deliveryAddress: {
206+
* address1: '123 Main St',
207+
* city: 'Anytown',
208+
* countryCode: 'US'
209+
* }
210+
* },
211+
* selected: true
212+
* }
213+
* ], { someOptionalParam: 'value' });
214+
*/
215+
replaceDeliveryAddresses: ReturnType<
216+
typeof cartDeliveryAddressesReplaceDefault
217+
>;
188218
};
189219

190220
export type HydrogenCartCustom<
@@ -316,6 +346,8 @@ export function createCartHandler<TCustomMethods extends CustomMethodsBase>(
316346
addDeliveryAddresses: cartDeliveryAddressesAddDefault(mutateOptions),
317347
removeDeliveryAddresses: cartDeliveryAddressesRemoveDefault(mutateOptions),
318348
updateDeliveryAddresses: cartDeliveryAddressesUpdateDefault(mutateOptions),
349+
replaceDeliveryAddresses:
350+
cartDeliveryAddressesReplaceDefault(mutateOptions),
319351
};
320352

321353
if ('customMethods' in options) {
@@ -414,6 +446,10 @@ export type HydrogenCartForDocs = {
414446
* Update cart delivery addresses.
415447
*/
416448
updateDeliveryAddresses?: CartDeliveryAddressesUpdateFunction;
449+
/**
450+
* Replace all delivery addresses on the cart.
451+
*/
452+
replaceDeliveryAddresses?: CartDeliveryAddressesReplaceFunction;
417453
/**
418454
* Updates additional information (attributes) in the cart.
419455
*/
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import {ReferenceEntityTemplateSchema} from '@shopify/generate-docs';
2+
3+
const data: ReferenceEntityTemplateSchema = {
4+
name: 'cartDeliveryAddressesReplace',
5+
category: 'utilities',
6+
subCategory: 'cart',
7+
isVisualComponent: false,
8+
related: [],
9+
description:
10+
'Creates a function that accepts an array of [CartSelectableAddressInput](/docs/api/storefront/2025-10/input-objects/CartSelectableAddressInput) to replace all delivery addresses on a cart',
11+
type: 'utility',
12+
defaultExample: {
13+
description:
14+
'Replace all delivery addresses on a cart with a new set of addresses',
15+
codeblock: {
16+
tabs: [
17+
{
18+
title: 'JavaScript',
19+
code: './cartDeliveryAddressesReplaceDefault.example.js',
20+
language: 'js',
21+
},
22+
],
23+
title: 'example',
24+
},
25+
},
26+
definitions: [
27+
{
28+
title: 'cartDeliveryAddressesReplaceDefault',
29+
type: 'CartDeliveryAddressesReplaceDefaultGeneratedType',
30+
description: '',
31+
},
32+
],
33+
};
34+
35+
export default data;
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import {cartDeliveryAddressesReplaceDefault} from '@shopify/hydrogen';
2+
3+
const replaceDeliveryAddresses = cartDeliveryAddressesReplaceDefault({
4+
storefront,
5+
getCartId,
6+
});
7+
8+
const result = await replaceDeliveryAddresses(
9+
[
10+
{
11+
address: {
12+
deliveryAddress: {
13+
address1: '<your-address1>',
14+
address2: '<your-address2>',
15+
city: '<your-city>',
16+
company: '<your-company>',
17+
countryCode: 'AC',
18+
firstName: '<your-firstName>',
19+
lastName: '<your-lastName>',
20+
phone: '<your-phone>',
21+
provinceCode: '<your-provinceCode>',
22+
zip: '<your-zip>',
23+
},
24+
},
25+
selected: true,
26+
},
27+
],
28+
{someOptionalParam: 'value'},
29+
);
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import {describe, it, expect} from 'vitest';
2+
import {CART_ID, mockCreateStorefrontClient} from '../cart-test-helper';
3+
import {cartDeliveryAddressesReplaceDefault} from './cartDeliveryAddressesReplaceDefault';
4+
5+
describe('cartDeliveryAddressesReplaceDefault', () => {
6+
it('should return a default cart delivery address replace implementation', async () => {
7+
const replaceDeliveryAddresses = cartDeliveryAddressesReplaceDefault({
8+
storefront: mockCreateStorefrontClient(),
9+
getCartId: () => CART_ID,
10+
});
11+
12+
const result = await replaceDeliveryAddresses([]);
13+
14+
expect(result.cart).toHaveProperty('id', CART_ID);
15+
});
16+
17+
it('can override cartFragment', async () => {
18+
const cartFragment = 'cartFragmentOverride';
19+
const replaceDeliveryAddresses = cartDeliveryAddressesReplaceDefault({
20+
storefront: mockCreateStorefrontClient(),
21+
getCartId: () => CART_ID,
22+
cartFragment,
23+
});
24+
25+
const result = await replaceDeliveryAddresses([]);
26+
27+
expect(result.cart).toHaveProperty('id', CART_ID);
28+
expect(result.userErrors?.[0]).toContain(cartFragment);
29+
});
30+
});
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
import {StorefrontApiErrors, formatAPIResult} from '../../storefront';
2+
import type {CartSelectableAddressInput} from '@shopify/hydrogen-react/storefront-api-types';
3+
import {
4+
CART_WARNING_FRAGMENT,
5+
MINIMAL_CART_FRAGMENT,
6+
USER_ERROR_FRAGMENT,
7+
} from './cart-fragments';
8+
import type {
9+
CartOptionalInput,
10+
CartQueryData,
11+
CartQueryDataReturn,
12+
CartQueryOptions,
13+
} from './cart-types';
14+
15+
export type CartDeliveryAddressesReplaceFunction = (
16+
addresses: Array<CartSelectableAddressInput>,
17+
optionalParams?: CartOptionalInput,
18+
) => Promise<CartQueryDataReturn>;
19+
20+
/**
21+
* Replaces all delivery addresses on the cart.
22+
*
23+
* This function sends a mutation to the storefront API to replace all delivery addresses on the cart
24+
* with the provided addresses. It returns the result of the mutation, including any errors that occurred.
25+
*
26+
* @param {CartQueryOptions} options - The options for the cart query, including the storefront API client and cart fragment.
27+
* @returns {CartDeliveryAddressesReplaceFunction} - A function that takes an array of addresses and optional parameters, and returns the result of the API call.
28+
*
29+
* @example
30+
* const replaceDeliveryAddresses = cartDeliveryAddressesReplaceDefault({ storefront, getCartId });
31+
* const result = await replaceDeliveryAddresses([
32+
* {
33+
* address: {
34+
* deliveryAddress: {
35+
* address1: '123 Main St',
36+
* city: 'Anytown',
37+
* countryCode: 'US'
38+
* }
39+
* },
40+
* selected: true
41+
* }
42+
* ], { someOptionalParam: 'value' }
43+
* );
44+
*/
45+
export function cartDeliveryAddressesReplaceDefault(
46+
options: CartQueryOptions,
47+
): CartDeliveryAddressesReplaceFunction {
48+
return async (
49+
addresses: Array<CartSelectableAddressInput>,
50+
optionalParams,
51+
) => {
52+
const {cartDeliveryAddressesReplace, errors} =
53+
await options.storefront.mutate<{
54+
cartDeliveryAddressesReplace: CartQueryData;
55+
errors: StorefrontApiErrors;
56+
}>(CART_DELIVERY_ADDRESSES_REPLACE_MUTATION(options.cartFragment), {
57+
variables: {
58+
cartId: options.getCartId(),
59+
addresses,
60+
...optionalParams,
61+
},
62+
});
63+
64+
return formatAPIResult(cartDeliveryAddressesReplace, errors);
65+
};
66+
}
67+
68+
//! @see: https://shopify.dev/docs/api/storefront/2025-10/mutations/cartDeliveryAddressesReplace
69+
export const CART_DELIVERY_ADDRESSES_REPLACE_MUTATION = (
70+
cartFragment = MINIMAL_CART_FRAGMENT,
71+
) => `#graphql
72+
mutation cartDeliveryAddressesReplace(
73+
$cartId: ID!
74+
$addresses: [CartSelectableAddressInput!]!,
75+
$country: CountryCode = ZZ
76+
$language: LanguageCode
77+
) @inContext(country: $country, language: $language) {
78+
cartDeliveryAddressesReplace(addresses: $addresses, cartId: $cartId) {
79+
cart {
80+
...CartApiMutation
81+
}
82+
userErrors {
83+
...CartApiError
84+
}
85+
warnings {
86+
...CartApiWarning
87+
}
88+
}
89+
}
90+
${cartFragment}
91+
${USER_ERROR_FRAGMENT}
92+
${CART_WARNING_FRAGMENT}
93+
`;

0 commit comments

Comments
 (0)