Skip to content
This repository has been archived by the owner on Mar 3, 2023. It is now read-only.

Commit

Permalink
Add additional functions to ShopifyProvider (#71)
Browse files Browse the repository at this point in the history
* Added new functions to shopify provider

* Update cart to use the new methods

Add changeset

* feedback updates
  • Loading branch information
frehner committed Nov 16, 2022
1 parent 0683765 commit ccfbbbd
Show file tree
Hide file tree
Showing 8 changed files with 219 additions and 55 deletions.
16 changes: 16 additions & 0 deletions .changeset/serious-crabs-chew.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
---
'@shopify/hydrogen-react': patch
---

Adds the functions `getStorefrontApiUrl()` and `getPublicTokenHeaders()` to the object returned by `useShop()` (and provided by `<ShopifyProvider/>`).

For example:

```ts
const {storefrontId, getPublicTokenHeaders, getStorefrontApiUrl} = useShop();

fetch(getStorefrontApiUrl(), {
headers: getPublicTokenHeaders({contentType: 'json'})
body: {...}
})
```
4 changes: 2 additions & 2 deletions packages/react/src/CartProvider.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import {ComponentProps, useState} from 'react';
import type {Story} from '@ladle/react';
import {CartProvider, storageAvailable, useCart} from './CartProvider.js';
import {ShopifyContextValue, ShopifyProvider} from './ShopifyProvider.js';
import {type ShopifyContextProps, ShopifyProvider} from './ShopifyProvider.js';
import {CART_ID_STORAGE_KEY} from './cart-constants.js';

const merchandiseId = 'gid://shopify/ProductVariant/41007290482744';
Expand Down Expand Up @@ -208,7 +208,7 @@ function CartComponent() {
);
}

const config: ShopifyContextValue = {
const config: ShopifyContextProps = {
storeDomain: 'hydrogen-preview.myshopify.com',
storefrontToken: '3b580e70970c4528da70c98e097c2fa0',
storefrontApiVersion: '2022-10',
Expand Down
5 changes: 2 additions & 3 deletions packages/react/src/ShopifyProvider.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,16 @@
import * as React from 'react';
import type {Story} from '@ladle/react';
import {
ShopifyProvider,
useShop,
type ShopifyContextValue,
type ShopifyContextProps,
} from './ShopifyProvider.js';

const Template: Story<{
storeDomain: string;
storefrontToken: string;
version: string;
}> = ({storeDomain, storefrontToken, version}) => {
const config: ShopifyContextValue = {
const config: ShopifyContextProps = {
storeDomain,
storefrontToken,
storefrontApiVersion: version,
Expand Down
105 changes: 101 additions & 4 deletions packages/react/src/ShopifyProvider.test.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
import * as React from 'react';
import {render, screen, renderHook} from '@testing-library/react';
import {
ShopifyProvider,
useShop,
type ShopifyContextValue,
type ShopifyContextProps,
} from './ShopifyProvider.js';
import type {PartialDeep} from 'type-fest';

const SHOPIFY_CONFIG: ShopifyContextValue = {
const SHOPIFY_CONFIG: ShopifyContextProps = {
storeDomain: 'notashop.myshopify.com',
storefrontToken: 'abc123',
storefrontApiVersion: '2022-10',
Expand Down Expand Up @@ -47,10 +46,108 @@ describe('<ShopifyProvider/>', () => {

expect(result.current.storeDomain).toBe('notashop.myshopify.com');
});

describe(`getStorefrontApiUrl()`, () => {
it(`returns the correct values`, () => {
const {result} = renderHook(() => useShop(), {
wrapper: ({children}) => (
<ShopifyProvider
shopifyConfig={{
...SHOPIFY_CONFIG,
storeDomain: 'https://notashop.myshopify.com',
}}
>
{children}
</ShopifyProvider>
),
});

expect(result.current.getStorefrontApiUrl()).toBe(
'https://notashop.myshopify.com/api/2022-10/graphql.json'
);
});

it(`allows overrides`, () => {
const {result} = renderHook(() => useShop(), {
wrapper: ({children}) => (
<ShopifyProvider
shopifyConfig={{
...SHOPIFY_CONFIG,
storeDomain: 'https://notashop.myshopify.com',
}}
>
{children}
</ShopifyProvider>
),
});

expect(
result.current.getStorefrontApiUrl({
storeDomain: 'override.myshopify.com',
storefrontApiVersion: '2022-07',
})
).toBe('https://override.myshopify.com/api/2022-07/graphql.json');
});
});

describe(`getPublicTokenHeaders()`, () => {
it(`returns the correct values`, () => {
const {result} = renderHook(() => useShop(), {
wrapper: ({children}) => (
<ShopifyProvider
shopifyConfig={{
...SHOPIFY_CONFIG,
storeDomain: 'https://notashop.myshopify.com',
}}
>
{children}
</ShopifyProvider>
),
});

expect(
result.current.getPublicTokenHeaders({contentType: 'json'})
).toEqual({
'X-SDK-Variant': 'hydrogen-ui',
'X-SDK-Variant-Source': 'react',
'X-SDK-Version': '2022-10',
'X-Shopify-Storefront-Access-Token': 'abc123',
'content-type': 'application/json',
});
});

it(`allows overrides`, () => {
const {result} = renderHook(() => useShop(), {
wrapper: ({children}) => (
<ShopifyProvider
shopifyConfig={{
...SHOPIFY_CONFIG,
storeDomain: 'https://notashop.myshopify.com',
}}
>
{children}
</ShopifyProvider>
),
});

expect(
result.current.getPublicTokenHeaders({
contentType: 'graphql',
storefrontToken: 'newtoken',
})
).toEqual({
'X-SDK-Variant': 'hydrogen-ui',
'X-SDK-Variant-Source': 'react',
'X-SDK-Version': '2022-10',
'X-Shopify-Storefront-Access-Token': 'newtoken',
'content-type': 'application/graphql',
});
});
});
});

export function getShopifyConfig(
config: PartialDeep<ShopifyContextValue, {recurseIntoArrays: true}> = {}
config: PartialDeep<ShopifyContextProps, {recurseIntoArrays: true}> = {}
) {
return {
country: {
Expand Down
70 changes: 62 additions & 8 deletions packages/react/src/ShopifyProvider.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {createContext, useContext, useMemo, type ReactNode} from 'react';
import type {LanguageCode, CountryCode, Shop} from './storefront-api-types.js';
import {SFAPI_VERSION} from './storefront-api-constants.js';
import {getPublicTokenHeadersRaw} from './storefront-client.js';

const ShopifyContext = createContext<ShopifyContextValue>({
storeDomain: 'test.myshopify.com',
Expand All @@ -13,6 +14,12 @@ const ShopifyContext = createContext<ShopifyContextValue>({
isoCode: 'EN',
},
locale: 'EN-US',
getStorefrontApiUrl() {
return '';
},
getPublicTokenHeaders() {
return {};
},
});

/**
Expand All @@ -23,7 +30,7 @@ export function ShopifyProvider({
shopifyConfig,
}: {
children: ReactNode;
shopifyConfig: ShopifyContextValue;
shopifyConfig: ShopifyContextProps;
}) {
if (!shopifyConfig) {
throw new Error(
Expand All @@ -37,13 +44,26 @@ export function ShopifyProvider({
);
}

const finalConfig = useMemo<ShopifyContextValue>(
() => ({
const finalConfig = useMemo<ShopifyContextValue>(() => {
const storeDomain = shopifyConfig.storeDomain.replace(/^https?:\/\//, '');
return {
...shopifyConfig,
storeDomain: shopifyConfig.storeDomain.replace(/^https?:\/\//, ''),
}),
[shopifyConfig]
);
storeDomain,
getPublicTokenHeaders(overrideProps) {
return getPublicTokenHeadersRaw(
overrideProps.contentType,
shopifyConfig.storefrontApiVersion,
overrideProps.storefrontToken ?? shopifyConfig.storefrontToken
);
},
getStorefrontApiUrl(overrideProps) {
return `https://${overrideProps?.storeDomain ?? storeDomain}/api/${
overrideProps?.storefrontApiVersion ??
shopifyConfig.storefrontApiVersion
}/graphql.json`;
},
};
}, [shopifyConfig]);

return (
<ShopifyContext.Provider value={finalConfig}>
Expand All @@ -66,7 +86,7 @@ export function useShop() {
/**
* Shopify-specific values that are used in various Hydrogen-UI components and hooks.
*/
export type ShopifyContextValue = {
export type ShopifyContextProps = {
/** The globally-unique identifier for the Shop */
storefrontId?: string;
/** The host name of the domain (eg: `{shop}.myshopify.com`). If a URL with a scheme (for example `https://`) is passed in, then the scheme is removed. */
Expand All @@ -92,3 +112,37 @@ export type ShopifyContextValue = {
*/
locale?: string;
};

export type ShopifyContextValue = ShopifyContextProps & {
/**
* Creates the fully-qualified URL to your store's GraphQL endpoint.
*
* By default, it will use the config you passed in when creating `<ShopifyProvider/>`. However, you can override the following settings on each invocation of `getStorefrontApiUrl({...})`:
*
* - `storeDomain`
* - `storefrontApiVersion`
*/
getStorefrontApiUrl: (props?: {
/** The host name of the domain (eg: `{shop}.myshopify.com`). */
storeDomain?: string;
/** The Storefront API version. This should almost always be the same as the version Hydrogen-UI was built for. Learn more about Shopify [API versioning](https://shopify.dev/api/usage/versioning) for more details. */
storefrontApiVersion?: string;
}) => string;
/**
* Returns an object that contains headers that are needed for each query to Storefront API GraphQL endpoint. This uses the public Storefront API token.
*
* By default, it will use the config you passed in when creating `<ShopifyProvider/>`. However, you can override the following settings on each invocation of `getPublicTokenHeaders({...})`:
*
* - `contentType`
* - `storefrontToken`
*
*/
getPublicTokenHeaders: (props: {
/**
* Customizes which `"content-type"` header is added when using `getPrivateTokenHeaders()` and `getPublicTokenHeaders()`. When fetching with a `JSON.stringify()`-ed `body`, use `"json"`. When fetching with a `body` that is a plain string, use `"graphql"`. Defaults to `"json"`
*/
contentType: 'json' | 'graphql';
/** The Storefront API access token. Refer to the [authentication](https://shopify.dev/api/storefront#authentication) documentation for more details. */
storefrontToken?: string;
}) => Record<string, string>;
};
2 changes: 0 additions & 2 deletions packages/react/src/cart-constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@ export const CART_ID_STORAGE_KEY = 'shopifyCartId';
export const CART_COOKIE_TTL_DAYS = 14;

// Needed for cart analytics within Shopify
export const STOREFRONT_API_PUBLIC_TOKEN_HEADER =
'X-Shopify-Storefront-Access-Token';
export const SHOPIFY_STOREFRONT_ID_HEADER = 'Shopify-Storefront-Id';
export const SHOPIFY_STOREFRONT_Y_HEADER = 'Shopify-Storefront-Y';
export const SHOPIFY_STOREFRONT_S_HEADER = 'Shopify-Storefront-S';
Expand Down
32 changes: 11 additions & 21 deletions packages/react/src/cart-hooks.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import {CartCreate, defaultCartFragment} from './cart-queries.js';
import {Cart} from './cart-types.js';
import {
SHOPIFY_STOREFRONT_ID_HEADER,
STOREFRONT_API_PUBLIC_TOKEN_HEADER,
SHOPIFY_STOREFRONT_Y_HEADER,
SHOPIFY_STOREFRONT_S_HEADER,
SHOPIFY_Y,
Expand All @@ -16,8 +15,7 @@ import {parse} from 'worktop/cookie';
import type {StorefrontApiResponseOkPartial} from './storefront-api-response.types.js';

export function useCartFetch() {
const {storeDomain, storefrontApiVersion, storefrontToken, storefrontId} =
useShop();
const {storefrontId, getPublicTokenHeaders, getStorefrontApiUrl} = useShop();

return useCallback(
<ReturnDataGeneric,>({
Expand All @@ -27,12 +25,7 @@ export function useCartFetch() {
query: string;
variables: Record<string, unknown>;
}): Promise<StorefrontApiResponseOkPartial<ReturnDataGeneric>> => {
const headers: Record<string, string> = {
'Content-Type': 'application/json',
'X-SDK-Variant': 'hydrogen',
'X-SDK-Version': storefrontApiVersion,
[STOREFRONT_API_PUBLIC_TOKEN_HEADER]: storefrontToken,
};
const headers = getPublicTokenHeaders({contentType: 'json'});

if (storefrontId) {
headers[SHOPIFY_STOREFRONT_ID_HEADER] = storefrontId;
Expand All @@ -45,17 +38,14 @@ export function useCartFetch() {
headers[SHOPIFY_STOREFRONT_S_HEADER] = cookieData[SHOPIFY_S];
}

return fetch(
`https://${storeDomain}/api/${storefrontApiVersion}/graphql.json`,
{
method: 'POST',
headers,
body: JSON.stringify({
query: query.toString(),
variables,
}),
}
)
return fetch(getStorefrontApiUrl(), {
method: 'POST',
headers,
body: JSON.stringify({
query: query.toString(),
variables,
}),
})
.then((res) => res.json())
.catch((error) => {
return {
Expand All @@ -64,7 +54,7 @@ export function useCartFetch() {
};
});
},
[storeDomain, storefrontApiVersion, storefrontToken, storefrontId]
[getPublicTokenHeaders, storefrontId, getStorefrontApiUrl]
);
}

Expand Down
Loading

0 comments on commit ccfbbbd

Please sign in to comment.