Skip to content

Commit 95672ae

Browse files
authored
Merge pull request #2539 from SalesforceCommerceCloud/feature/handle-missing-sdk-clients
@W-18568190@ - Handle missing SDK Clients in CommerceApiProviders
2 parents f61bfc9 + a8b3a66 commit 95672ae

File tree

35 files changed

+219
-110
lines changed

35 files changed

+219
-110
lines changed

packages/commerce-sdk-react/CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
## v3.4.0-dev.0 (May 23, 2025)
22
- Now compatible with either React 17 and 18 [#2506](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/2506)
3-
3+
- Gracefully handle missing SDK Clients in CommerceApiProvider [#2539](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/2539)
44
- Refactor commerce-sdk-react to allow injecting ApiClients [#2519](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/2519)
55

66
## v3.3.0 (May 22, 2025)

packages/commerce-sdk-react/src/components/ShopperExperience/types.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,13 @@
55
* For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause
66
*/
77

8+
import {CLIENT_KEYS} from '../../constant'
89
import {ApiClients, DataType} from '../../hooks/types' // TODO: Should we be moving these types to a more global place.
910

1011
type ArrayElement<ArrayType extends readonly unknown[]> = ArrayType[number]
1112

12-
type Client = ApiClients['shopperExperience']
13+
const CLIENT_KEY = CLIENT_KEYS.SHOPPER_EXPERIENCE
14+
type Client = NonNullable<ApiClients[typeof CLIENT_KEY]>
1315

1416
export type Page = DataType<Client['getPage']>
1517

packages/commerce-sdk-react/src/components/StorefrontPreview/storefront-preview.test.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -117,9 +117,9 @@ describe('Storefront Preview Component', function () {
117117
const parameters = {basketId: '123'}
118118
const MockedComponent = ({enableStorefrontPreview}: {enableStorefrontPreview: boolean}) => {
119119
const apiClients = useCommerceApi()
120-
getBasketSpy = jest.spyOn(apiClients.shopperBaskets, 'getBasket')
120+
getBasketSpy = jest.spyOn(apiClients.shopperBaskets!, 'getBasket')
121121
useEffect(() => {
122-
void apiClients.shopperBaskets.getBasket({parameters})
122+
void apiClients.shopperBaskets!.getBasket({parameters})
123123
}, [])
124124
return (
125125
<StorefrontPreview enabled={enableStorefrontPreview} getToken={() => 'my-token'} />

packages/commerce-sdk-react/src/components/StorefrontPreview/utils.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ describe('Storefront Preview utils', () => {
101101
test('proxy handlers applied to all client methods', async () => {
102102
const {result} = renderHookWithProviders(() => useCommerceApi())
103103
const clients = result.current
104-
const shopperBaskets = clients.shopperBaskets
104+
const shopperBaskets = clients.shopperBaskets!
105105
const handlers = {apply: jest.fn()}
106106

107107
proxyRequests(clients, handlers)

packages/commerce-sdk-react/src/constant.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,3 +49,18 @@ export const EXCLUDE_COOKIE_SUFFIX = [DWSID_COOKIE_NAME, DNT_COOKIE_NAME]
4949
* Use the header key below to send dwsid value with SCAPI/OCAPI requests.
5050
*/
5151
export const SERVER_AFFINITY_HEADER_KEY = 'sfdc_dwsid'
52+
53+
export const CLIENT_KEYS = {
54+
SHOPPER_BASKETS: 'shopperBaskets',
55+
SHOPPER_CONTEXTS: 'shopperContexts',
56+
SHOPPER_CUSTOMERS: 'shopperCustomers',
57+
SHOPPER_EXPERIENCE: 'shopperExperience',
58+
SHOPPER_GIFT_CERTIFICATES: 'shopperGiftCertificates',
59+
SHOPPER_LOGIN: 'shopperLogin',
60+
SHOPPER_ORDERS: 'shopperOrders',
61+
SHOPPER_PRODUCTS: 'shopperProducts',
62+
SHOPPER_PROMOTIONS: 'shopperPromotions',
63+
SHOPPER_SEARCH: 'shopperSearch',
64+
SHOPPER_SEO: 'shopperSeo',
65+
SHOPPER_STORES: 'shopperStores'
66+
} as const

packages/commerce-sdk-react/src/hooks/ShopperBaskets/cache.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,10 @@ import {
2121
getTaxesFromBasket
2222
} from './queryKeyHelpers'
2323
import {getCustomerBaskets} from '../ShopperCustomers/queryKeyHelpers'
24+
import {CLIENT_KEYS} from '../../constant'
2425

25-
type Client = ApiClients['shopperBaskets']
26+
const CLIENT_KEY = CLIENT_KEYS.SHOPPER_BASKETS
27+
type Client = NonNullable<ApiClients[typeof CLIENT_KEY]>
2628
/** Data returned by every Shopper Baskets endpoint (except `deleteBasket`) */
2729
type Basket = ShopperBasketsTypes.Basket
2830
/** Data returned by `getCustomerBaskets` */

packages/commerce-sdk-react/src/hooks/ShopperBaskets/helpers.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,10 @@ import {useCustomerId, useShopperBasketsMutation} from '../index'
99
import {useCustomerBaskets} from '../ShopperCustomers'
1010
import {ApiClients, Argument} from '../types'
1111
import {ShopperBasketsTypes} from 'commerce-sdk-isomorphic'
12-
type Client = ApiClients['shopperBaskets']
12+
import {CLIENT_KEYS} from '../../constant'
13+
14+
const CLIENT_KEY = CLIENT_KEYS.SHOPPER_BASKETS
15+
type Client = NonNullable<ApiClients[typeof CLIENT_KEY]>
1316
type Basket = ShopperBasketsTypes.Basket
1417

1518
/**

packages/commerce-sdk-react/src/hooks/ShopperBaskets/mutation.test.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,14 +22,16 @@ import {useCustomerBaskets} from '../ShopperCustomers'
2222
import {ApiClients, Argument} from '../types'
2323
import {ShopperBasketsMutation, useShopperBasketsMutation} from './mutation'
2424
import * as queries from './query'
25+
import {CLIENT_KEYS} from '../../constant'
2526

2627
jest.mock('../../auth/index.ts', () => {
2728
const {default: mockAuth} = jest.requireActual('../../auth/index.ts')
2829
mockAuth.prototype.ready = jest.fn().mockResolvedValue({access_token: 'access_token'})
2930
return mockAuth
3031
})
3132

32-
type Client = ApiClients['shopperBaskets']
33+
const CLIENT_KEY = CLIENT_KEYS.SHOPPER_BASKETS
34+
type Client = NonNullable<ApiClients[typeof CLIENT_KEY]>
3335
type Basket = ShopperBasketsTypes.Basket
3436
type BasketsResult = ShopperCustomersTypes.BasketsResult
3537

@@ -52,8 +54,9 @@ const newBasket: Basket = {basketId: BASKET_ID, mockData: 'new basket'}
5254
// --- getCustomerBaskets constants --- //
5355
const customersEndpoint = '/customer/shopper-customers/'
5456
const CUSTOMER_ID = 'customer_id'
55-
// Can't use `makeOptions()` here because it's Shopper Customers, not Shopper Baskets
56-
const getCustomerBasketsOptions: Argument<ApiClients['shopperCustomers']['getCustomerBaskets']> = {
57+
const getCustomerBasketsOptions: Argument<
58+
NonNullable<ApiClients['shopperCustomers']>['getCustomerBaskets']
59+
> = {
5760
parameters: {
5861
customerId: CUSTOMER_ID
5962
}

packages/commerce-sdk-react/src/hooks/ShopperBaskets/mutation.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,12 @@
77
import {ApiClients, ApiMethod, Argument, CacheUpdateGetter, DataType, MergedOptions} from '../types'
88
import {useMutation} from '../useMutation'
99
import {UseMutationResult} from '@tanstack/react-query'
10-
import useCommerceApi from '../useCommerceApi'
1110
import {cacheUpdateMatrix} from './cache'
11+
import {CLIENT_KEYS} from '../../constant'
12+
import useCommerceApi from '../useCommerceApi'
1213

13-
type Client = ApiClients['shopperBaskets']
14+
const CLIENT_KEY = CLIENT_KEYS.SHOPPER_BASKETS
15+
type Client = NonNullable<ApiClients[typeof CLIENT_KEY]>
1416

1517
/**
1618
* Mutations available for Shopper Baskets.
@@ -185,7 +187,7 @@ export function useShopperBasketsMutation<Mutation extends ShopperBasketsMutatio
185187
// I'm not sure if there's a way to avoid the type assertions in here for the methods that
186188
// use them. However, I'm fairly confident that they are safe to do, as they seem to be simply
187189
// re-asserting what we already have.
188-
const {shopperBaskets: client} = useCommerceApi()
190+
const client = useCommerceApi(CLIENT_KEY)
189191
type Options = Argument<Client[Mutation]>
190192
type Data = DataType<Client[Mutation]>
191193
return useMutation({

packages/commerce-sdk-react/src/hooks/ShopperBaskets/query.ts

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,15 @@
66
*/
77
import {UseQueryResult} from '@tanstack/react-query'
88
import {ApiClients, ApiQueryOptions, Argument, DataType, NullableParameters} from '../types'
9-
import useCommerceApi from '../useCommerceApi'
109
import {useQuery} from '../useQuery'
1110
import {mergeOptions, omitNullableParameters, pickValidParams} from '../utils'
1211
import * as queryKeyHelpers from './queryKeyHelpers'
1312
import {ShopperBaskets} from 'commerce-sdk-isomorphic'
13+
import {CLIENT_KEYS} from '../../constant'
14+
import useCommerceApi from '../useCommerceApi'
1415

15-
type Client = ApiClients['shopperBaskets']
16+
const CLIENT_KEY = CLIENT_KEYS.SHOPPER_BASKETS
17+
type Client = NonNullable<ApiClients[typeof CLIENT_KEY]>
1618

1719
/**
1820
* Gets a basket.
@@ -31,7 +33,7 @@ export const useBasket = (
3133
): UseQueryResult<DataType<Client['getBasket']>> => {
3234
type Options = Argument<Client['getBasket']>
3335
type Data = DataType<Client['getBasket']>
34-
const {shopperBaskets: client} = useCommerceApi()
36+
const client = useCommerceApi(CLIENT_KEY)
3537
const methodName = 'getBasket'
3638
const requiredParameters = ShopperBaskets.paramKeys[`${methodName}Required`]
3739

@@ -73,7 +75,7 @@ export const usePaymentMethodsForBasket = (
7375
): UseQueryResult<DataType<Client['getPaymentMethodsForBasket']>> => {
7476
type Options = Argument<Client['getPaymentMethodsForBasket']>
7577
type Data = DataType<Client['getPaymentMethodsForBasket']>
76-
const {shopperBaskets: client} = useCommerceApi()
78+
const client = useCommerceApi(CLIENT_KEY)
7779
const methodName = 'getPaymentMethodsForBasket'
7880
const requiredParameters = ShopperBaskets.paramKeys[`${methodName}Required`]
7981

@@ -115,7 +117,7 @@ export const usePriceBooksForBasket = (
115117
): UseQueryResult<DataType<Client['getPriceBooksForBasket']>> => {
116118
type Options = Argument<Client['getPriceBooksForBasket']>
117119
type Data = DataType<Client['getPriceBooksForBasket']>
118-
const {shopperBaskets: client} = useCommerceApi()
120+
const client = useCommerceApi(CLIENT_KEY)
119121
const methodName = 'getPriceBooksForBasket'
120122
const requiredParameters = ShopperBaskets.paramKeys[`${methodName}Required`]
121123

@@ -157,7 +159,7 @@ export const useShippingMethodsForShipment = (
157159
): UseQueryResult<DataType<Client['getShippingMethodsForShipment']>> => {
158160
type Options = Argument<Client['getShippingMethodsForShipment']>
159161
type Data = DataType<Client['getShippingMethodsForShipment']>
160-
const {shopperBaskets: client} = useCommerceApi()
162+
const client = useCommerceApi(CLIENT_KEY)
161163
const methodName = 'getShippingMethodsForShipment'
162164
const requiredParameters = ShopperBaskets.paramKeys[`${methodName}Required`]
163165

@@ -199,7 +201,7 @@ export const useTaxesFromBasket = (
199201
): UseQueryResult<DataType<Client['getTaxesFromBasket']>> => {
200202
type Options = Argument<Client['getTaxesFromBasket']>
201203
type Data = DataType<Client['getTaxesFromBasket']>
202-
const {shopperBaskets: client} = useCommerceApi()
204+
const client = useCommerceApi(CLIENT_KEY)
203205
const methodName = 'getTaxesFromBasket'
204206
const requiredParameters = ShopperBaskets.paramKeys[`${methodName}Required`]
205207

0 commit comments

Comments
 (0)