UNPKG

@shopify/hydrogen-react

Version:

React components, hooks, and utilities for creating custom Shopify storefronts

194 lines (193 loc) • 6.69 kB
import { jsx } from "react/jsx-runtime"; import { useMemo, useState, useEffect, useCallback, useContext, createContext } from "react"; import { flattenConnection } from "./flatten-connection.mjs"; const ProductOptionsContext = createContext(null); function ProductProvider({ children, data: product, initialVariantId: explicitVariantId }) { const variants = useMemo( () => flattenConnection(product.variants ?? {}), [product.variants] ); if (!isProductVariantArray(variants)) { throw new Error( `<ProductProvider/> requires 'product.variants.nodes' or 'product.variants.edges'` ); } const options = useMemo(() => getOptions(variants), [variants]); const [selectedVariant, setSelectedVariant] = useState(() => getVariantBasedOnIdProp(explicitVariantId, variants)); const [selectedOptions, setSelectedOptions] = useState( () => getSelectedOptions(selectedVariant) ); useEffect(() => { const newSelectedVariant = getVariantBasedOnIdProp( explicitVariantId, variants ); setSelectedVariant(newSelectedVariant); setSelectedOptions(getSelectedOptions(newSelectedVariant)); }, [explicitVariantId, variants]); const setSelectedOption = useCallback( (name, value2) => { setSelectedOptions((selectedOptions2) => { const opts = { ...selectedOptions2, [name]: value2 }; setSelectedVariant(getSelectedVariant(variants, opts)); return opts; }); }, [setSelectedOptions, variants] ); const isOptionInStock = useCallback( (option, value2) => { const proposedVariant = getSelectedVariant(variants, { ...selectedOptions, ...{ [option]: value2 } }); return (proposedVariant == null ? void 0 : proposedVariant.availableForSale) ?? true; }, [selectedOptions, variants] ); const sellingPlanGroups = useMemo( () => flattenConnection(product.sellingPlanGroups ?? {}).map( (sellingPlanGroup) => ({ ...sellingPlanGroup, sellingPlans: flattenConnection((sellingPlanGroup == null ? void 0 : sellingPlanGroup.sellingPlans) ?? {}) }) ), [product.sellingPlanGroups] ); const [selectedSellingPlan, setSelectedSellingPlan] = useState(void 0); const selectedSellingPlanAllocation = useMemo(() => { var _a, _b; if (!selectedVariant || !selectedSellingPlan) { return; } if (!((_a = selectedVariant.sellingPlanAllocations) == null ? void 0 : _a.nodes) && !((_b = selectedVariant.sellingPlanAllocations) == null ? void 0 : _b.edges)) { throw new Error( `<ProductProvider/>: You must include 'sellingPlanAllocations.nodes' or 'sellingPlanAllocations.edges' in your variants in order to calculate selectedSellingPlanAllocation` ); } return flattenConnection(selectedVariant.sellingPlanAllocations).find( (allocation) => { var _a2; return ((_a2 = allocation == null ? void 0 : allocation.sellingPlan) == null ? void 0 : _a2.id) === selectedSellingPlan.id; } ); }, [selectedVariant, selectedSellingPlan]); const value = useMemo( () => ({ product, variants, variantsConnection: product.variants, options, selectedVariant, setSelectedVariant, selectedOptions, setSelectedOption, setSelectedOptions, isOptionInStock, selectedSellingPlan, setSelectedSellingPlan, selectedSellingPlanAllocation, sellingPlanGroups, sellingPlanGroupsConnection: product.sellingPlanGroups }), [ product, isOptionInStock, options, selectedOptions, selectedSellingPlan, selectedSellingPlanAllocation, selectedVariant, sellingPlanGroups, setSelectedOption, variants ] ); return /* @__PURE__ */ jsx(ProductOptionsContext.Provider, { value, children }); } function useProduct() { const context = useContext(ProductOptionsContext); if (!context) { throw new Error(`'useProduct' must be a child of <ProductProvider />`); } return context; } function getSelectedVariant(variants, choices) { var _a, _b; if (!variants.length || ((_b = (_a = variants == null ? void 0 : variants[0]) == null ? void 0 : _a.selectedOptions) == null ? void 0 : _b.length) !== Object.keys(choices).length) { return; } return variants == null ? void 0 : variants.find((variant) => { return Object.entries(choices).every(([name, value]) => { var _a2; return (_a2 = variant == null ? void 0 : variant.selectedOptions) == null ? void 0 : _a2.some( (option) => (option == null ? void 0 : option.name) === name && (option == null ? void 0 : option.value) === value ); }); }); } function getOptions(variants) { const map = variants.reduce( (memo, variant) => { var _a; if (!variant.selectedOptions) { throw new Error(`'getOptions' requires 'variant.selectedOptions'`); } (_a = variant == null ? void 0 : variant.selectedOptions) == null ? void 0 : _a.forEach((opt) => { memo[(opt == null ? void 0 : opt.name) ?? ""] = memo[(opt == null ? void 0 : opt.name) ?? ""] || /* @__PURE__ */ new Set(); memo[(opt == null ? void 0 : opt.name) ?? ""].add((opt == null ? void 0 : opt.value) ?? ""); }); return memo; }, {} ); return Object.keys(map).map((option) => { return { name: option, values: Array.from(map[option]) }; }); } function getVariantBasedOnIdProp(explicitVariantId, variants) { if (explicitVariantId) { const foundVariant = variants.find( (variant) => (variant == null ? void 0 : variant.id) === explicitVariantId ); if (!foundVariant) { console.warn( `<ProductProvider/> received a 'initialVariantId' prop, but could not actually find a variant with that ID` ); } return foundVariant; } if (explicitVariantId === null) { return null; } if (explicitVariantId === void 0) { return variants.find((variant) => variant == null ? void 0 : variant.availableForSale) || variants[0]; } } function getSelectedOptions(selectedVariant) { return (selectedVariant == null ? void 0 : selectedVariant.selectedOptions) ? selectedVariant.selectedOptions.reduce( (memo, optionSet) => { memo[(optionSet == null ? void 0 : optionSet.name) ?? ""] = (optionSet == null ? void 0 : optionSet.value) ?? ""; return memo; }, {} ) : {}; } function isProductVariantArray(maybeVariantArray) { if (!maybeVariantArray || !Array.isArray(maybeVariantArray)) { return false; } return true; } export { ProductProvider, useProduct }; //# sourceMappingURL=ProductProvider.mjs.map