Skip to content

Commit

Permalink
Add the raw product returned from the Storefront API to also return f…
Browse files Browse the repository at this point in the history
…rom useProduct (#735)
  • Loading branch information
blittle committed Mar 31, 2023
1 parent 25f78ab commit 5e26503
Show file tree
Hide file tree
Showing 7 changed files with 204 additions and 8 deletions.
18 changes: 18 additions & 0 deletions .changeset/few-yaks-admire.md
@@ -0,0 +1,18 @@
---
'@shopify/hydrogen-react': patch
---

Add the raw product returned from the Storefront API to also return from `useProduct()`:

```ts
function SomeComponent() {
const {product} = useProduct();

return (
<div>
<h2>{product.title}</h2>
<h3>{product.description}</h3>
</div>
);
}
```
94 changes: 92 additions & 2 deletions packages/hydrogen-react/docs/generated/generated_docs_data.json
Expand Up @@ -4626,12 +4626,12 @@
"tabs": [
{
"title": "JavaScript",
"code": "import {ProductProvider} from '@shopify/hydrogen-react';\n\nexport function Product({product}) {\n return (\n <ProductProvider data={product} initialVariantId=\"some-id\">\n {/* Your JSX */}\n </ProductProvider>\n );\n}\n",
"code": "import {ProductProvider, useProduct} from '@shopify/hydrogen-react';\n\nexport function Product({product}) {\n return (\n <ProductProvider data={product} initialVariantId=\"some-id\">\n <UsingProduct />\n </ProductProvider>\n );\n}\n\nfunction UsingProduct() {\n const {product, variants, setSelectedVariant} = useProduct();\n return (\n <>\n <h1>{product?.title}</h1>\n {variants?.map((variant) => {\n <button onClick={() => setSelectedVariant(variant)} key={variant?.id}>\n {variant?.title}\n </button>;\n })}\n ;\n </>\n );\n}\n",
"language": "jsx"
},
{
"title": "TypeScript",
"code": "import {ProductProvider} from '@shopify/hydrogen-react';\nimport type {Product} from '@shopify/hydrogen-react/storefront-api-types';\n\nexport function Product({product}: {product: Product}) {\n return (\n <ProductProvider data={product} initialVariantId=\"some-id\">\n {/* Your JSX */}\n </ProductProvider>\n );\n}\n",
"code": "import {ProductProvider, useProduct} from '@shopify/hydrogen-react';\nimport type {Product} from '@shopify/hydrogen-react/storefront-api-types';\n\nexport function Product({product}: {product: Product}) {\n return (\n <ProductProvider data={product} initialVariantId=\"some-id\">\n <UsingProduct />\n </ProductProvider>\n );\n}\n\nfunction UsingProduct() {\n const {product, variants, setSelectedVariant} = useProduct();\n return (\n <>\n <h1>{product?.title}</h1>\n {variants?.map((variant) => {\n <button onClick={() => setSelectedVariant(variant)} key={variant?.id}>\n {variant?.title}\n </button>;\n })}\n ;\n </>\n );\n}\n",
"language": "tsx"
}
],
Expand Down Expand Up @@ -6794,6 +6794,96 @@
}
]
},
{
"name": "useProduct",
"category": "hooks",
"isVisualComponent": false,
"related": [
{
"name": "ProductProvider",
"type": "component",
"url": "/api/hydrogen-react/components/productprovider"
}
],
"description": "Provides access to the product value passed to `<ProductProvider />`. It also includes properties for selecting product variants, options, and selling plans.",
"type": "hook",
"defaultExample": {
"description": "I am the default example",
"codeblock": {
"tabs": [
{
"title": "JavaScript",
"code": "import {ProductProvider, useProduct} from '@shopify/hydrogen-react';\n\nexport function Product({product}) {\n return (\n <ProductProvider data={product} initialVariantId=\"some-id\">\n <UsingProduct />\n </ProductProvider>\n );\n}\n\nfunction UsingProduct() {\n const {product, variants, setSelectedVariant} = useProduct();\n return (\n <>\n <h1>{product?.title}</h1>\n {variants?.map((variant) => {\n <button onClick={() => setSelectedVariant(variant)} key={variant?.id}>\n {variant?.title}\n </button>;\n })}\n ;\n </>\n );\n}\n",
"language": "jsx"
},
{
"title": "TypeScript",
"code": "import {ProductProvider, useProduct} from '@shopify/hydrogen-react';\nimport type {Product} from '@shopify/hydrogen-react/storefront-api-types';\n\nexport function Product({product}: {product: Product}) {\n return (\n <ProductProvider data={product} initialVariantId=\"some-id\">\n <UsingProduct />\n </ProductProvider>\n );\n}\n\nfunction UsingProduct() {\n const {product, variants, setSelectedVariant} = useProduct();\n return (\n <>\n <h1>{product?.title}</h1>\n {variants?.map((variant) => {\n <button onClick={() => setSelectedVariant(variant)} key={variant?.id}>\n {variant?.title}\n </button>;\n })}\n ;\n </>\n );\n}\n",
"language": "tsx"
}
],
"title": "Example code"
}
},
"definitions": [
{
"title": "Props",
"description": "`useProduct` must be a descendent of the `<ProductProvider />` component.",
"type": "UseProductGeneratedType",
"typeDefinitions": {
"UseProductGeneratedType": {
"filePath": "/ProductProvider.tsx",
"name": "UseProductGeneratedType",
"params": [],
"returns": {
"filePath": "/ProductProvider.tsx",
"description": "",
"name": "ProductHookValue",
"value": "ProductHookValue"
},
"value": "export function useProduct(): ProductHookValue {\n const context = useContext(ProductOptionsContext);\n\n if (!context) {\n throw new Error(`'useProduct' must be a child of <ProductProvider />`);\n }\n\n return context;\n}"
},
"ProductHookValue": {
"filePath": "/ProductProvider.tsx",
"syntaxKind": "TypeAliasDeclaration",
"name": "ProductHookValue",
"value": "PartialDeep<\n {\n /** The raw product from the Storefront API */\n product: Product;\n /** An array of the variant `nodes` from the `VariantConnection`. */\n variants: ProductVariantType[];\n variantsConnection?: ProductVariantConnection;\n /** An array of the product's options and values. */\n options: OptionWithValues[];\n /** The selected variant. */\n selectedVariant?: ProductVariantType | null;\n selectedOptions: SelectedOptions;\n /** The selected selling plan. */\n selectedSellingPlan?: SellingPlanType;\n /** The selected selling plan allocation. */\n selectedSellingPlanAllocation?: SellingPlanAllocationType;\n /** The selling plan groups. */\n sellingPlanGroups?: (Omit<SellingPlanGroupType, 'sellingPlans'> & {\n sellingPlans: SellingPlanType[];\n })[];\n sellingPlanGroupsConnection?: SellingPlanGroupConnection;\n },\n {recurseIntoArrays: true}\n> & {\n /** A callback to set the selected variant to the variant passed as an argument. */\n setSelectedVariant: (\n variant: PartialDeep<ProductVariantType, {recurseIntoArrays: true}> | null,\n ) => void;\n /** A callback to set the selected option. */\n setSelectedOption: (\n name: SelectedOptionType['name'],\n value: SelectedOptionType['value'],\n ) => void;\n /** A callback to set multiple selected options at once. */\n setSelectedOptions: (options: SelectedOptions) => void;\n /** A callback to set the selected selling plan to the one passed as an argument. */\n setSelectedSellingPlan: (\n sellingPlan: PartialDeep<SellingPlanType, {recurseIntoArrays: true}>,\n ) => void;\n /** A callback that returns a boolean indicating if the option is in stock. */\n isOptionInStock: (\n name: SelectedOptionType['name'],\n value: SelectedOptionType['value'],\n ) => boolean;\n}",
"description": ""
},
"OptionWithValues": {
"filePath": "/ProductProvider.tsx",
"name": "OptionWithValues",
"description": "",
"members": [
{
"filePath": "/ProductProvider.tsx",
"syntaxKind": "PropertySignature",
"name": "name",
"value": "string",
"description": ""
},
{
"filePath": "/ProductProvider.tsx",
"syntaxKind": "PropertySignature",
"name": "values",
"value": "string[]",
"description": ""
}
],
"value": "export interface OptionWithValues {\n name: SelectedOptionType['name'];\n values: SelectedOptionType['value'][];\n}"
},
"SelectedOptions": {
"filePath": "/ProductProvider.tsx",
"syntaxKind": "TypeAliasDeclaration",
"name": "SelectedOptions",
"value": "{\n [key: string]: string;\n}",
"description": "",
"members": []
}
}
}
]
},
{
"name": "useShop",
"category": "hooks",
Expand Down
19 changes: 17 additions & 2 deletions packages/hydrogen-react/src/ProductProvider.example.jsx
@@ -1,9 +1,24 @@
import {ProductProvider} from '@shopify/hydrogen-react';
import {ProductProvider, useProduct} from '@shopify/hydrogen-react';

export function Product({product}) {
return (
<ProductProvider data={product} initialVariantId="some-id">
{/* Your JSX */}
<UsingProduct />
</ProductProvider>
);
}

function UsingProduct() {
const {product, variants, setSelectedVariant} = useProduct();
return (
<>
<h1>{product?.title}</h1>
{variants?.map((variant) => {
<button onClick={() => setSelectedVariant(variant)} key={variant?.id}>
{variant?.title}
</button>;
})}
;
</>
);
}
19 changes: 17 additions & 2 deletions packages/hydrogen-react/src/ProductProvider.example.tsx
@@ -1,10 +1,25 @@
import {ProductProvider} from '@shopify/hydrogen-react';
import {ProductProvider, useProduct} from '@shopify/hydrogen-react';
import type {Product} from '@shopify/hydrogen-react/storefront-api-types';

export function Product({product}: {product: Product}) {
return (
<ProductProvider data={product} initialVariantId="some-id">
{/* Your JSX */}
<UsingProduct />
</ProductProvider>
);
}

function UsingProduct() {
const {product, variants, setSelectedVariant} = useProduct();
return (
<>
<h1>{product?.title}</h1>
{variants?.map((variant) => {
<button onClick={() => setSelectedVariant(variant)} key={variant?.id}>
{variant?.title}
</button>;
})}
;
</>
);
}
11 changes: 11 additions & 0 deletions packages/hydrogen-react/src/ProductProvider.test.tsx
Expand Up @@ -43,6 +43,17 @@ describe('<ProductProvider />', () => {
]);
});

it('returns full product', () => {
const product = getProduct({variants: VARIANTS});
const {result} = renderHook(() => useProduct(), {
wrapper: ({children}) => (
<ProductProvider data={product}>{children}</ProductProvider>
),
});

expect(result.current.product).toEqual(product);
});

it('provides setSelectedOption callback', async () => {
const user = userEvent.setup();

Expand Down
6 changes: 4 additions & 2 deletions packages/hydrogen-react/src/ProductProvider.tsx
Expand Up @@ -167,6 +167,7 @@ export function ProductProvider({

const value = useMemo<ProductHookValue>(
() => ({
product,
variants,
variantsConnection: product.variants,
options,
Expand All @@ -183,10 +184,9 @@ export function ProductProvider({
sellingPlanGroupsConnection: product.sellingPlanGroups,
}),
[
product,
isOptionInStock,
options,
product.sellingPlanGroups,
product.variants,
selectedOptions,
selectedSellingPlan,
selectedSellingPlanAllocation,
Expand Down Expand Up @@ -335,6 +335,8 @@ export interface OptionWithValues {

type ProductHookValue = PartialDeep<
{
/** The raw product from the Storefront API */
product: Product;
/** An array of the variant `nodes` from the `VariantConnection`. */
variants: ProductVariantType[];
variantsConnection?: ProductVariantConnection;
Expand Down
45 changes: 45 additions & 0 deletions packages/hydrogen-react/src/useProduct.doc.ts
@@ -0,0 +1,45 @@
import {ReferenceEntityTemplateSchema} from '@shopify/generate-docs';

const data: ReferenceEntityTemplateSchema = {
name: 'useProduct',
category: 'hooks',
isVisualComponent: false,
related: [
{
name: 'ProductProvider',
type: 'component',
url: '/api/hydrogen-react/components/productprovider',
},
],
description:
'Provides access to the product value passed to `<ProductProvider />`. It also includes properties for selecting product variants, options, and selling plans.',
type: 'hook',
defaultExample: {
description: 'I am the default example',
codeblock: {
tabs: [
{
title: 'JavaScript',
code: './ProductProvider.example.jsx',
language: 'jsx',
},
{
title: 'TypeScript',
code: './ProductProvider.example.tsx',
language: 'tsx',
},
],
title: 'Example code',
},
},
definitions: [
{
title: 'Props',
type: 'UseProductGeneratedType',
description:
'`useProduct` must be a descendent of the `<ProductProvider />` component.',
},
],
};

export default data;

0 comments on commit 5e26503

Please sign in to comment.