Skip to main content

ProductContext

Description​

Provides product page data to child components. Used on product detail pages to access product information, variants, pricing, inventory, and attributes.

Import​

import { ProductProvider, useProduct } from '@components/frontStore/catalog/ProductContext';

Usage​

Setup Provider​

import { ProductProvider } from '@components/frontStore/catalog/ProductContext';

function ProductPage({ product }) {
return (
<ProductProvider product={product}>
{/* Product page components */}
</ProductProvider>
);
}

Access Product Data​

import { useProduct } from '@components/frontStore/catalog/ProductContext';

function ProductInfo() {
const product = useProduct();

return (
<div>
<h1>{product.name}</h1>
<p>{product.price.regular.text}</p>
{product.inventory.isInStock ? (
<span>In Stock</span>
) : (
<span>Out of Stock</span>
)}
</div>
);
}

API​

ProductProvider Props​

NameTypeRequiredDescription
productProductDataYesProduct data object
childrenReactNodeYesChild components

useProduct Hook​

Returns the complete ProductData object. Throws error if used outside ProductProvider.

ProductData Interface​

FieldTypeDescription
productIdnumberProduct ID
uuidstringProduct UUID
namestringProduct name
descriptionArray<Row>Rich text description
skustringProduct SKU
priceProductPriceDataRegular and special pricing
inventoryobjectStock status
weightobjectWeight value and unit
urlstringProduct URL
imageImageDataMain product image
galleryImageData[]Product gallery images
attributesAttributeIndexItem[]Product attributes
variantGroupVariantGroupProduct variants data

Type Definitions​

ProductPriceData​

interface ProductPriceData {
regular: {
value: number; // Price value
text: string; // Formatted price
};
special?: {
value: number; // Sale price value
text: string; // Formatted sale price
};
}

VariantGroup​

interface VariantGroup {
variantAttributes: Array<{
attributeId: number;
attributeCode: string;
attributeName: string;
options: Array<{
optionId: number;
optionText: string;
productId?: number;
}>;
}>;
items: Array<{
attributes: Array<{
attributeCode: string;
optionId: number;
}>;
product?: {
productId: number;
name: string;
sku: string;
url: string;
price: ProductPriceData;
image?: ImageData;
};
}>;
}

Examples​

Product Header​

import { useProduct } from '@components/frontStore/catalog/ProductContext';
import { Image } from '@components/common/Image';

function ProductHeader() {
const { name, sku, price, image } = useProduct();

return (
<div className="product-header">
{image && <Image src={image.url} alt={image.alt || name} width={600} height={600} />}
<div>
<h1>{name}</h1>
<p className="sku">SKU: {sku}</p>
<div className="price">
{price.special ? (
<>
<span className="special-price">{price.special.text}</span>
<span className="regular-price line-through">{price.regular.text}</span>
</>
) : (
<span className="regular-price">{price.regular.text}</span>
)}
</div>
</div>
</div>
);
}

Stock Status​

import { useProduct } from '@components/frontStore/catalog/ProductContext';

function StockStatus() {
const { inventory } = useProduct();

return (
<div className="stock-status">
{inventory.isInStock ? (
<span className="in-stock">✓ In Stock</span>
) : (
<span className="out-of-stock">Out of Stock</span>
)}
</div>
);
}
import { useProduct } from '@components/frontStore/catalog/ProductContext';
import { Image } from '@components/common/Image';

function ProductGallery() {
const { gallery } = useProduct();

if (!gallery || gallery.length === 0) {
return null;
}

return (
<div className="product-gallery">
{gallery.map((image, index) => (
<Image
key={index}
src={image.url}
alt={image.alt || `Gallery image ${index + 1}`}
width={100}
height={100}
/>
))}
</div>
);
}

Product Attributes​

import { useProduct } from '@components/frontStore/catalog/ProductContext';

function ProductAttributes() {
const { attributes } = useProduct();

if (!attributes || attributes.length === 0) {
return null;
}

return (
<div className="product-attributes">
<h3>Specifications</h3>
<dl>
{attributes.map((attr, index) => (
<div key={index}>
<dt>{attr.attributeName}</dt>
<dd>{attr.optionText}</dd>
</div>
))}
</dl>
</div>
);
}

Variant Selector​

import { useProduct } from '@components/frontStore/catalog/ProductContext';
import { useState } from 'react';

function VariantSelector() {
const { variantGroup } = useProduct();
const [selectedOptions, setSelectedOptions] = useState<Record<string, number>>({});

if (!variantGroup) {
return null;
}

const handleOptionChange = (attributeCode: string, optionId: number) => {
setSelectedOptions(prev => ({
...prev,
[attributeCode]: optionId
}));
};

return (
<div className="variant-selector">
{variantGroup.variantAttributes.map(attribute => (
<div key={attribute.attributeCode} className="variant-attribute">
<label>{attribute.attributeName}</label>
<div className="options">
{attribute.options.map(option => (
<button
key={option.optionId}
onClick={() => handleOptionChange(attribute.attributeCode, option.optionId)}
className={selectedOptions[attribute.attributeCode] === option.optionId ? 'active' : ''}
>
{option.optionText}
</button>
))}
</div>
</div>
))}
</div>
);
}

Complete Product Page​

import { ProductProvider, useProduct } from '@components/frontStore/catalog/ProductContext';
import { Image } from '@components/common/Image';
import { AddToCart } from '@components/frontStore/cart/AddToCart';

function ProductPageContent() {
const product = useProduct();
const { name, sku, description, price, inventory, image, gallery, attributes, weight } = product;

return (
<div className="product-page">
<div className="product-main">
{/* Gallery */}
<div className="product-gallery">
{image && (
<Image src={image.url} alt={image.alt || name} width={600} height={600} />
)}
{gallery && gallery.length > 0 && (
<div className="thumbnails">
{gallery.map((img, i) => (
<Image key={i} src={img.url} alt={img.alt} width={100} height={100} />
))}
</div>
)}
</div>

{/* Product Info */}
<div className="product-info">
<h1>{name}</h1>
<p className="sku">SKU: {sku}</p>

{/* Price */}
<div className="price">
{price.special ? (
<>
<span className="special">{price.special.text}</span>
<span className="regular line-through">{price.regular.text}</span>
</>
) : (
<span className="regular">{price.regular.text}</span>
)}
</div>

{/* Stock */}
<div className="stock">
{inventory.isInStock ? '✓ In Stock' : 'Out of Stock'}
</div>

{/* Add to Cart */}
<AddToCart
product={{ sku, isInStock: inventory.isInStock }}
qty={1}
>
{/* children is (state, actions) => ReactNode — two arguments,
not one destructured object. */}
{(state, actions) => (
<button
onClick={actions.addToCart}
disabled={!state.canAddToCart || state.isLoading}
>
{state.isLoading ? 'Adding...' : 'Add to Cart'}
</button>
)}
</AddToCart>

{/* Description */}
{description && (
<div className="description">
<h2>Description</h2>
<div>{description}</div>
</div>
)}

{/* Attributes */}
{attributes && attributes.length > 0 && (
<div className="attributes">
<h2>Specifications</h2>
<dl>
{attributes.map((attr, i) => (
<div key={i}>
<dt>{attr.attributeName}</dt>
<dd>{attr.optionText}</dd>
</div>
))}
</dl>
</div>
)}

{/* Weight */}
{weight && (
<p className="weight">
Weight: {weight.value} {weight.unit}
</p>
)}
</div>
</div>
</div>
);
}

export default function ProductPage({ productData }) {
return (
<ProductProvider product={productData}>
<ProductPageContent />
</ProductProvider>
);
}

Features​

  • Product Metadata: Name, SKU, description, URL
  • Pricing: Regular and special (sale) prices
  • Inventory: Stock availability status
  • Images: Main image and gallery support
  • Attributes: Product specifications
  • Variants: Multi-attribute product variants
  • Weight: Product weight with unit
  • Extended Fields: Support for custom fields
  • Type Safe: Full TypeScript support
  • Error Handling: Throws error if used outside provider

Usage Notes​

  • Only available on product detail pages
  • Must wrap components in ProductProvider
  • The useProduct hook throws error if used outside provider
  • Description is rich text (array of Row objects)
  • Price.special is optional (only present for sale items)
  • Gallery and attributes are optional arrays
  • Extended fields allow custom product data