Your onAddToCart
The one piece you write — the signature, the Shopify variant-id mapping, and how to test it without the live feed.
Everything else on your side is configuration. onAddToCart is the one piece of behaviour
you write: the feed calls it when a shopper clicks Add-to-Cart on a recommendation card,
and you run your own cart mutation.
onAddToCart={async ({variantId, quantity}) => {
await addToCart({variantId, quantity: quantity ?? 1}); // your cart mutation
// …update your own cart UI/state here…
}}addToCart above stands in for however your storefront adds a line item — a REST or
GraphQL call, a wrapper client, or a framework helper. Each
framework guide shows the idiomatic shape for that framework: a
Next.js Server Action, a React Router action, a TanStack Start server function, or a
plain client-side fetch.
The contract
type OnAddToCart = (item: {variantId: string; quantity: number}) => void | Promise<void>;variantId— the id of the card's variant as identified in your Product Genius catalog, always a string.quantity— how many to add. The examples here default it to1defensively; that costs nothing and keeps the handler correct if it is ever invoked without one.- The handler may be sync or async. Return a promise if you await your mutation.
- You own the cart mutation and the resulting UI. Product Genius does not touch your cart, your cart state, or your drawer — it only tells you what was clicked.
You can pass a fresh inline arrow on every render; the feed component reads the latest handler through a ref, so it never re-injects the script or re-registers the handler.
onAddToCart only fires on stores configured for a headless cart — that routing is set
on the Product Genius side by your contact. If cards render but Add-to-Cart buttons are
missing, or clicking one does nothing, see the
triage table.
Shopify variant ids
The one Product-Genius-specific line in most integrations.
Numeric id in, gid out
variantId arrives as the feed's numeric Shopify variant id. The Storefront API cart
mutation wants a gid — gid://shopify/ProductVariant/<id>. Map it before you submit.
const merchandiseId = String(variantId).includes('gid://')
? String(variantId)
: `gid://shopify/ProductVariant/${variantId}`;The full worked version lives in the Hydrogen reference integration, which wires this
through Hydrogen's CartForm:
const cartFetcher = useFetcher({key: 'pg-add-to-cart'});
const {open} = useAside();
<ProductGeniusFeed
/* …shop, pageConfig… */
onAddToCart={({variantId, quantity}) => {
// The feed sends a numeric variant id; the Storefront cart wants a gid.
const merchandiseId = String(variantId).includes('gid://')
? String(variantId)
: `gid://shopify/ProductVariant/${variantId}`;
cartFetcher.submit(
{
[CartForm.INPUT_NAME]: JSON.stringify({
action: CartForm.ACTIONS.LinesAdd,
inputs: {lines: [{merchandiseId, quantity: quantity ?? 1}]},
}),
},
{method: 'POST', action: '/cart'},
);
open('cart');
}}
/>CartForm comes from @shopify/hydrogen, useFetcher from react-router, and useAside
is the Hydrogen skeleton's cart-drawer hook. The id mapping is the only Product Genius line
in there; the rest is ordinary Hydrogen cart code.
Testing your handler
You do not need a live feed to test your cart handler. <ProductGeniusFeed> registers it at
window.pgEventHandlers.onAddToCart, and that global is the test seam — invoke it exactly
the way the feed does on a card click.
Unit — your handler is a plain function. Test it directly with its cart dependency
mocked: assert it maps variantId correctly and issues the right cart mutation.
Integration — render the component, then call the registered handler:
import {act, render} from '@testing-library/react';
import {ProductGeniusFeed} from '@productgenius/react';
it('adds the variant to the cart', async () => {
const onAddToCart = vi.fn();
render(<ProductGeniusFeed shop="store.example.com" onAddToCart={onAddToCart} />);
// what the feed does on an Add-to-Cart click:
await act(async () => {
await window.pgEventHandlers.onAddToCart({variantId: '123', quantity: 1});
});
expect(onAddToCart).toHaveBeenCalledWith({variantId: '123', quantity: 1});
});If your handler performs a real request (a server action or route handler), intercept it with MSW and assert the mutation payload.
End-to-end — on a storefront whose Product Genius org is configured for a headless cart, load a product page, click a recommendation card's Add-to-Cart, and assert the cart updates. This exercises the live feed, so run it as a staging smoke test rather than a unit gate.
The same global doubles as a console one-liner for debugging a handler without clicking through the feed:
window.pgEventHandlers.onAddToCart({variantId: '123', quantity: 1});How is this guide?
ProductGeniusFeed
Render the Product Genius recommendation feed on your product page — the render example, every prop, and the two gotchas that bite.
ProductGeniusSearchBar
The Product Genius search bar mount for React storefronts — a working native search styled as a skeleton that the live bar replaces.