@shopify/hydrogen-react
Version:
React components, hooks, and utilities for creating custom Shopify storefronts
399 lines (398 loc) • 14.5 kB
JavaScript
;
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
const jsxRuntime = require("react/jsx-runtime");
const React = require("react");
const useCartAPIStateMachine = require("./useCartAPIStateMachine.js");
const cartConstants = require("./cart-constants.js");
const cartQueries = require("./cart-queries.js");
const ShopifyProvider = require("./ShopifyProvider.js");
const CartContext = React.createContext(null);
function useCart() {
const context = React.useContext(CartContext);
if (!context) {
throw new Error("Expected a Cart Context, but no Cart Context was found");
}
return context;
}
function CartProvider({
children,
numCartLines,
onCreate,
onLineAdd,
onLineRemove,
onLineUpdate,
onNoteUpdate,
onBuyerIdentityUpdate,
onAttributesUpdate,
onDiscountCodesUpdate,
onCreateComplete,
onLineAddComplete,
onLineRemoveComplete,
onLineUpdateComplete,
onNoteUpdateComplete,
onBuyerIdentityUpdateComplete,
onAttributesUpdateComplete,
onDiscountCodesUpdateComplete,
data: cart,
cartFragment = cartQueries.defaultCartFragment,
customerAccessToken,
countryCode,
languageCode
}) {
var _a, _b, _c, _d, _e, _f, _g;
const shop = ShopifyProvider.useShop();
if (!shop)
throw new Error(
"<CartProvider> needs to be a descendant of <ShopifyProvider>"
);
countryCode = (countryCode ?? shop.countryIsoCode ?? "US").toUpperCase();
languageCode = (languageCode ?? shop.languageIsoCode ?? "EN").toUpperCase();
if (countryCode) countryCode = countryCode.toUpperCase();
const [prevCountryCode, setPrevCountryCode] = React.useState(countryCode);
const [prevCustomerAccessToken, setPrevCustomerAccessToken] = React.useState(customerAccessToken);
const customerOverridesCountryCode = React.useRef(false);
if (prevCountryCode !== countryCode || prevCustomerAccessToken !== customerAccessToken) {
setPrevCountryCode(countryCode);
setPrevCustomerAccessToken(customerAccessToken);
customerOverridesCountryCode.current = false;
}
const [cartState, cartSend] = useCartAPIStateMachine.useCartAPIStateMachine({
numCartLines,
data: cart,
cartFragment,
countryCode,
languageCode,
onCartActionEntry(_, event) {
try {
switch (event.type) {
case "CART_CREATE":
return onCreate == null ? void 0 : onCreate();
case "CARTLINE_ADD":
return onLineAdd == null ? void 0 : onLineAdd();
case "CARTLINE_REMOVE":
return onLineRemove == null ? void 0 : onLineRemove();
case "CARTLINE_UPDATE":
return onLineUpdate == null ? void 0 : onLineUpdate();
case "NOTE_UPDATE":
return onNoteUpdate == null ? void 0 : onNoteUpdate();
case "BUYER_IDENTITY_UPDATE":
return onBuyerIdentityUpdate == null ? void 0 : onBuyerIdentityUpdate();
case "CART_ATTRIBUTES_UPDATE":
return onAttributesUpdate == null ? void 0 : onAttributesUpdate();
case "DISCOUNT_CODES_UPDATE":
return onDiscountCodesUpdate == null ? void 0 : onDiscountCodesUpdate();
}
} catch (error) {
console.error("Cart entry action failed", error);
}
},
onCartActionOptimisticUI(context, event) {
var _a2, _b2, _c2, _d2;
if (!context.cart) return { ...context };
switch (event.type) {
case "CARTLINE_REMOVE":
return {
...context,
cart: {
...context.cart,
lines: (_b2 = (_a2 = context == null ? void 0 : context.cart) == null ? void 0 : _a2.lines) == null ? void 0 : _b2.filter(
(line) => (line == null ? void 0 : line.id) && !event.payload.lines.includes(line == null ? void 0 : line.id)
)
}
};
case "CARTLINE_UPDATE":
return {
...context,
cart: {
...context.cart,
lines: (_d2 = (_c2 = context == null ? void 0 : context.cart) == null ? void 0 : _c2.lines) == null ? void 0 : _d2.map((line) => {
const updatedLine = event.payload.lines.find(
({ id }) => id === (line == null ? void 0 : line.id)
);
if (updatedLine && updatedLine.quantity) {
return {
...line,
quantity: updatedLine.quantity
};
}
return line;
})
}
};
}
return { ...context };
},
onCartActionComplete(context, event) {
const cartActionEvent = event.payload.cartActionEvent;
try {
switch (event.type) {
case "RESOLVE":
switch (cartActionEvent.type) {
case "CART_CREATE":
return onCreateComplete == null ? void 0 : onCreateComplete();
case "CARTLINE_ADD":
return onLineAddComplete == null ? void 0 : onLineAddComplete();
case "CARTLINE_REMOVE":
return onLineRemoveComplete == null ? void 0 : onLineRemoveComplete();
case "CARTLINE_UPDATE":
return onLineUpdateComplete == null ? void 0 : onLineUpdateComplete();
case "NOTE_UPDATE":
return onNoteUpdateComplete == null ? void 0 : onNoteUpdateComplete();
case "BUYER_IDENTITY_UPDATE":
if (countryCodeNotUpdated(context, cartActionEvent)) {
customerOverridesCountryCode.current = true;
}
return onBuyerIdentityUpdateComplete == null ? void 0 : onBuyerIdentityUpdateComplete();
case "CART_ATTRIBUTES_UPDATE":
return onAttributesUpdateComplete == null ? void 0 : onAttributesUpdateComplete();
case "DISCOUNT_CODES_UPDATE":
return onDiscountCodesUpdateComplete == null ? void 0 : onDiscountCodesUpdateComplete();
}
}
} catch (error) {
console.error("onCartActionComplete failed", error);
}
}
});
const cartReady = React.useRef(false);
const [isCartReady, setIsCartReady] = React.useState(false);
const cartCompleted = cartState.matches("cartCompleted");
const countryChanged = (cartState.value === "idle" || cartState.value === "error" || cartState.value === "cartCompleted") && countryCode !== ((_c = (_b = (_a = cartState == null ? void 0 : cartState.context) == null ? void 0 : _a.cart) == null ? void 0 : _b.buyerIdentity) == null ? void 0 : _c.countryCode) && !cartState.context.errors;
const fetchingFromStorage = React.useRef(false);
React.useEffect(() => {
if (!cartReady.current && !fetchingFromStorage.current) {
if (!cart && storageAvailable("localStorage")) {
fetchingFromStorage.current = true;
try {
const cartId = window.localStorage.getItem(cartConstants.CART_ID_STORAGE_KEY);
if (cartId) {
cartSend({ type: "CART_FETCH", payload: { cartId } });
}
} catch (error) {
console.warn("error fetching cartId");
console.warn(error);
}
}
cartReady.current = true;
setIsCartReady(true);
}
}, [cart, cartReady, cartSend]);
React.useEffect(() => {
if (!countryChanged || customerOverridesCountryCode.current) return;
cartSend({
type: "BUYER_IDENTITY_UPDATE",
payload: { buyerIdentity: { countryCode, customerAccessToken } }
});
}, [
countryCode,
customerAccessToken,
countryChanged,
customerOverridesCountryCode,
cartSend
]);
const onCartReadySend = React.useCallback(
(cartEvent) => {
if (!cartReady.current) {
return console.warn("Cart isn't ready yet");
}
cartSend(cartEvent);
},
[cartSend]
);
React.useEffect(() => {
var _a2, _b2, _c2;
if (((_b2 = (_a2 = cartState == null ? void 0 : cartState.context) == null ? void 0 : _a2.cart) == null ? void 0 : _b2.id) && storageAvailable("localStorage")) {
try {
window.localStorage.setItem(
cartConstants.CART_ID_STORAGE_KEY,
(_c2 = cartState.context.cart) == null ? void 0 : _c2.id
);
} catch (error) {
console.warn("Failed to save cartId to localStorage", error);
}
}
}, [(_e = (_d = cartState == null ? void 0 : cartState.context) == null ? void 0 : _d.cart) == null ? void 0 : _e.id]);
React.useEffect(() => {
if (cartCompleted && storageAvailable("localStorage")) {
try {
window.localStorage.removeItem(cartConstants.CART_ID_STORAGE_KEY);
} catch (error) {
console.warn("Failed to delete cartId from localStorage", error);
}
}
}, [cartCompleted]);
const cartCreate = React.useCallback(
(cartInput) => {
var _a2, _b2;
if (countryCode && !((_a2 = cartInput.buyerIdentity) == null ? void 0 : _a2.countryCode)) {
if (cartInput.buyerIdentity == null) {
cartInput.buyerIdentity = {};
}
cartInput.buyerIdentity.countryCode = countryCode;
}
if (customerAccessToken && !((_b2 = cartInput.buyerIdentity) == null ? void 0 : _b2.customerAccessToken)) {
if (cartInput.buyerIdentity == null) {
cartInput.buyerIdentity = {};
}
cartInput.buyerIdentity.customerAccessToken = customerAccessToken;
}
onCartReadySend({
type: "CART_CREATE",
payload: cartInput
});
},
[countryCode, customerAccessToken, onCartReadySend]
);
const cartDisplayState = useDelayedStateUntilHydration(cartState);
const cartContextValue = React.useMemo(() => {
var _a2, _b2, _c2, _d2;
return {
...((_a2 = cartDisplayState == null ? void 0 : cartDisplayState.context) == null ? void 0 : _a2.cart) ?? { lines: [], attributes: [] },
status: transposeStatus(cartDisplayState.value),
error: (_b2 = cartDisplayState == null ? void 0 : cartDisplayState.context) == null ? void 0 : _b2.errors,
totalQuantity: ((_d2 = (_c2 = cartDisplayState == null ? void 0 : cartDisplayState.context) == null ? void 0 : _c2.cart) == null ? void 0 : _d2.totalQuantity) ?? 0,
cartCreate,
cartReady: isCartReady,
linesAdd(lines) {
var _a3, _b3;
if ((_b3 = (_a3 = cartDisplayState == null ? void 0 : cartDisplayState.context) == null ? void 0 : _a3.cart) == null ? void 0 : _b3.id) {
onCartReadySend({
type: "CARTLINE_ADD",
payload: { lines }
});
} else {
cartCreate({ lines });
}
},
linesRemove(lines) {
onCartReadySend({
type: "CARTLINE_REMOVE",
payload: {
lines
}
});
},
linesUpdate(lines) {
onCartReadySend({
type: "CARTLINE_UPDATE",
payload: {
lines
}
});
},
noteUpdate(note) {
onCartReadySend({
type: "NOTE_UPDATE",
payload: {
note
}
});
},
buyerIdentityUpdate(buyerIdentity) {
onCartReadySend({
type: "BUYER_IDENTITY_UPDATE",
payload: {
buyerIdentity
}
});
},
cartAttributesUpdate(attributes) {
onCartReadySend({
type: "CART_ATTRIBUTES_UPDATE",
payload: {
attributes
}
});
},
discountCodesUpdate(discountCodes) {
onCartReadySend({
type: "DISCOUNT_CODES_UPDATE",
payload: {
discountCodes
}
});
},
cartFragment
};
}, [
cartCreate,
isCartReady,
(_f = cartDisplayState == null ? void 0 : cartDisplayState.context) == null ? void 0 : _f.cart,
(_g = cartDisplayState == null ? void 0 : cartDisplayState.context) == null ? void 0 : _g.errors,
cartDisplayState.value,
cartFragment,
onCartReadySend
]);
return /* @__PURE__ */ jsxRuntime.jsx(CartContext.Provider, { value: cartContextValue, children });
}
function transposeStatus(status) {
switch (status) {
case "uninitialized":
case "initializationError":
return "uninitialized";
case "idle":
case "cartCompleted":
case "error":
return "idle";
case "cartFetching":
return "fetching";
case "cartCreating":
return "creating";
case "cartLineAdding":
case "cartLineRemoving":
case "cartLineUpdating":
case "noteUpdating":
case "buyerIdentityUpdating":
case "cartAttributesUpdating":
case "discountCodesUpdating":
return "updating";
}
}
function useDelayedStateUntilHydration(state) {
const [isPending, startTransition] = React.useTransition();
const [delayedState, setDelayedState] = React.useState(state);
const firstTimePending = React.useRef(false);
if (isPending) {
firstTimePending.current = true;
}
const firstTimePendingFinished = React.useRef(false);
if (!isPending && firstTimePending.current) {
firstTimePendingFinished.current = true;
}
React.useEffect(() => {
startTransition(() => {
if (!firstTimePendingFinished.current) {
setDelayedState(state);
}
});
}, [state]);
const displayState = firstTimePendingFinished.current ? state : delayedState;
return displayState;
}
function storageAvailable(type) {
let storage;
try {
storage = window[type];
const x = "__storage_test__";
storage.setItem(x, x);
storage.removeItem(x);
return true;
} catch (e) {
return !!(e instanceof DOMException && // everything except Firefox
(e.code === 22 || // Firefox
e.code === 1014 || // test name field too, because code might not be present
// everything except Firefox
e.name === "QuotaExceededError" || // Firefox
e.name === "NS_ERROR_DOM_QUOTA_REACHED") && // acknowledge QuotaExceededError only if there's something already stored
storage && storage.length !== 0);
}
}
function countryCodeNotUpdated(context, event) {
var _a, _b;
return !!(event.payload.buyerIdentity.countryCode && ((_b = (_a = context.cart) == null ? void 0 : _a.buyerIdentity) == null ? void 0 : _b.countryCode) !== event.payload.buyerIdentity.countryCode);
}
exports.CartContext = CartContext;
exports.CartProvider = CartProvider;
exports.storageAvailable = storageAvailable;
exports.useCart = useCart;
//# sourceMappingURL=CartProvider.js.map