RFC: Hydrogen Cart #823
Replies: 17 comments 37 replies
-
A few thoughts:
|
Beta Was this translation helpful? Give feedback.
-
I recently have been creating a cart via the cart setup documentation and it's been a bit of a headache so it's great to see this abstraction proposal! One of the things I'm trying to accomplish is use of the |
Beta Was this translation helpful? Give feedback.
-
I feel the heading I think it would be clearer as |
Beta Was this translation helpful? Give feedback.
-
Definitely agree on the need here. Really liking the After our first few h2 builds I'm feeling like modifying the cart fragment / query is going to be very common (likely every build) whereas the actions will be pretty consistent (since they dispatch a relatively constrained set of mutations) I quite liked the way an override cart query could be specified in H1, maybe that's an optional input of I also wouldn't hate if the cart route became a bit more magical as long as you could specify custom actions but also understand the reluctance. Makes me wonder if the default actions could just be handled automagically but devs would have the ability to specify custom actions (with different names) that had a different behavior. My sense is in the overwhelming majority of cases the default action set will be fine as long as the fragment can be updated, I'm not sure we've ever done a headless build where the cart fragment didn't need to be touched. |
Beta Was this translation helpful? Give feedback.
-
What about something like the following? const cart = createCartApi(storefront, request, {
addLine: () => {} // override existing method
cutomMethods: {
doMagic: () => { storefront.mutate(...) } // Add custom methods
}
});
cart.addLine(...); // Modified
cart.updateLines(...); // Default
cart.doMagic(...); // Custom This still allows anyone to copy/paste and make their own I feel I've mentioned this somewhere else but just in case, passing |
Beta Was this translation helpful? Give feedback.
-
Some thoughts here: regards to naming conventions, I believe it would be beneficial to have consistent API names with the Liquid cart form. For example, export function BuyNowButton({ variant }) {
const formInputs = {
items: [
{
merchandiseId: variant.id,
quantity: 1,
},
],
};
return (
<CartForm formInput={formInputs} action={CartFormInputAction.CartLinesAdd}>
<button>Buy now</button>
</CartForm>
);
} Please note that my familiarity with Hydrogen APIs is limited, and I may be mistaken. |
Beta Was this translation helpful? Give feedback.
-
@duncan-fairley Thanks for the feedbacks We have been going back and forth about providing an overridable cart fragment and here are the considerations: If we allow for overridable cart fragments, there need to be some restriction on the provided cart fragment For example:
If at any point the provided cart fragment is incorrect, debugging this is hard because there is no single place where the entire graphql query for const MY_CART_QUERY_FRAGMENT = `#graphql
fragment CartFragment on Cart {
id
checkoutUrl
totalQuantity
lines(first: $numCartLines) {
edges {
node {
id
}
}
}
}
`;
createCartApi({
storefront,
request,
cartQueryFragment: MY_CART_QUERY_FRAGMENT
}); It would be an anti-pattern if this cart abstraction prevents you from doing what graphql (the language itself) can do on its own So .. the question would be where do we draw the line between too much magic or not enough magic? |
Beta Was this translation helpful? Give feedback.
-
After some discussion within the team, we have arrive at this version of // Basic usage
const cart = createCartApi({
storefront,
requestHeaders: request.headers,
});
// Override cart fragments
const cart = createCartApi({
storefront,
requestHeaders: request.headers,
cartQueryFragment: CART_QUERY_FRAGMENT, // override cart fragment for cartGetDefault
cartMutateFragment: CART_MUTATE_FRAGMENT, // override cart fragment for all other mutation queries except for metafieldsSet and metafieldDelete
});
// Add or override cart methods
const cart = createCartApi({
storefront,
requestHeaders: request.headers,
customMethods: {
addLine: () => {
return await storefront.mutate(...),
}, // overrides cart.addLine
magic: () => {}, // adds a new method and can be invoked with `cart.magic()`
}
}); The user supplied cart fragments will have strict rules of the fragment name and variable usage. See #786 for implementation details |
Beta Was this translation helpful? Give feedback.
-
Had a random thought on this. We've had a long history of significant issues on international headless sites that have arisen from the store locale and We've started to roll out a change to the demo store export async function getCart(storefront: Storefront, cartId: string) {
let { cart } = await storefront.query<CartQuery>(CART_QUERY, {
variables: {
cartId,
country: storefront.i18n.country,
language: storefront.i18n.language,
},
cache: storefront.CacheNone(),
})
//If there is a mismatch between the country code in the cart buyerIdentity and the request,
//update the cart buyerIdentity to match the request
const cartBuyerCountryCode = cart?.buyerIdentity?.countryCode
const requestCountryCode = storefront?.i18n?.country
if (cartBuyerCountryCode && requestCountryCode && cartBuyerCountryCode !== requestCountryCode) {
const { cartBuyerIdentityUpdate } = await storefront.mutate<CartBuyerIdentityUpdateMutation>(
CART_BUYER_IDENTITY_UPDATE_MUTATION,
{
variables: {
cartId,
buyerIdentity: { countryCode: requestCountryCode },
},
},
)
cart = cartBuyerIdentityUpdate?.cart
}
return cart
} I'm waffling on whether something like this would make sense inside the abstraction or implemented in userland. I know the barrier to solving this issue on the merchant / agency side is high since you quickly get into relatively complex issues around loader state and validation to avoid infinite loops. Though this solution above definitely feels like a hack but we've had trouble handling it gracefully with an action (though I'm sure there is a way). This method does feel more robust and harder to break since it will always default to the app's locale. |
Beta Was this translation helpful? Give feedback.
-
Just found this repo. As someone who has done headless e-commerce for a few years now, I think your concerns about doing the cart on the client side are misplaced and the benefits of doing cart mutations on the server are overstated and complicate issues which are already solved. For applications in React (which this is), I am pretty sure the better option that actually is more scaleable and will actually work well in production is client-side using libraries like React-Query (tanstack query) or useSwr. But may React Query doesn't work well in Remix? Will be interesting to compare a client-side implementation of the Cart vs the server option. |
Beta Was this translation helpful? Give feedback.
-
The cart abstraction is available on To test it out, in your
Official docs are coming soon, here are some quick step to get you started: Step 1: Create a
|
Beta Was this translation helpful? Give feedback.
-
Released cart abstraction under |
Beta Was this translation helpful? Give feedback.
-
If interested, I have also started a discussion thread about optimistic UI for |
Beta Was this translation helpful? Give feedback.
-
Hey! I updated our One small bit of feedback was that it'd be great to be able to pass more props to the |
Beta Was this translation helpful? Give feedback.
-
Curious .. anyone here is using We are about to upgrade to storefront API |
Beta Was this translation helpful? Give feedback.
-
Not sure if I should post it here or create a new post :) We want to have the cart synchronized between the customer's devices, and for this we're saving the ID in the customer's metafield. Sadly the Example: const cart = createCartHandler({
storefront,
getCartId: async () => {
const {customer} = await storefront.query(/* Customer cartId query with some short cache */);
return customer.cartId;
},
setCartId: (cartId) => {
waitUntil(() => storefront.mutation(/* cartId mutation */));
return {};
},
}); |
Beta Was this translation helpful? Give feedback.
-
@chrisvltn I see what you are trying to do but the approach you have will be very unstable and becomes a query performance bottleneck for every cart queries and mutations in the project. It doesn't make sense for In your situation, you should call for query for customer metafield for that stored cartId upon logging in and store that cartId in a cookie header (or use |
Beta Was this translation helpful? Give feedback.
-
A proposal for a Hydrogen cart abstraction optimized for Remix.
Background
Hydrogen v2 in Remix does not currently provide an easy path for setting up a Cart:
hydrogen-react
's componentCartProvider
. This component works fully client side, but it is not optimized for Remix and server rendering.We think that providing a Remix optimized Cart abstraction should make it a lot easier to setup a cart and improve performance.
Proposal
Setup
createCartApi
to the Remix context inserver.ts
:if you are using TypeScript, make sure to add your
CartApiReturn
type to `remix.env.d.ts:CartForm
to your UI. For example on a product page:Advanced (optional) - Customize cart queries and logic
Sometimes, what we have as default isn’t what you need. You can completely customize your cart api however you want it.
createCartApi
into your projectPut the following code into your project in a file call
/app/lib/cart-queries.server.ts
:createCartApi
import in Remix context inserver.ts
createCartApi
Use the original
cartGetDefault
function and the graphql query as a starting point.[TBD] Simpler customize cart queries and logic
We've also researched an alternative way to customize default cart queries. We are researching this approach and will update this RFC when we are more convinced to add this to the framework.
const cart = createCartApi(storefront, request) + const extendedCart = {…cart, getCart: () => {/* custom stuffs */}}
Cart fragments
We initially don't plan on allowing modification of the default cart fragment since that would make the cart functions signature complex.
We'd love ❤️ to hear your feedback
Beta Was this translation helpful? Give feedback.
All reactions