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
| Name | Type | Required | Description |
|---|---|---|---|
| product | ProductData | Yes | Product data object |
| children | ReactNode | Yes | Child components |
useProduct Hook
Returns the complete ProductData object. Throws error if used outside ProductProvider.
ProductData Interface
| Field | Type | Description |
|---|---|---|
| productId | number | Product ID |
| uuid | string | Product UUID |
| name | string | Product name |
| description | Array<Row> | Rich text description |
| sku | string | Product SKU |
| price | ProductPriceData | Regular and special pricing |
| inventory | object | Stock status |
| weight | object | Weight value and unit |
| url | string | Product URL |
| image | ImageData | Main product image |
| gallery | ImageData[] | Product gallery images |
| attributes | AttributeIndexItem[] | Product attributes |
| variantGroup | VariantGroup | Product 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>
);
}
Product Gallery
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}
>
{({ addToCart, isLoading, canAddToCart }) => (
<button
onClick={addToCart}
disabled={!canAddToCart || isLoading}
>
{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
useProducthook 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
Related Components
- AddToCart - Add to cart component
- CategoryContext - Category page context
- Image - Image component