# cart.jsx
import { useLoaderData, data } from 'react-router';
import { CartForm } from '@shopify/hydrogen';
import { CartMain } from '~/components/CartMain';
/**
* @type {Route.MetaFunction}
*/
export const meta = () => {
return [{ title: `Tyresnmore | Cart` }];
};
/**
* @type {HeadersFunction}
*/
export const headers = ({ actionHeaders }) => actionHeaders;
/**
* @param {Route.ActionArgs}
*/
export async function action({ request, context }) {
const { cart, storefront } = context;
const formData = await request.formData();
const { action, inputs } = CartForm.getFormInput(formData);
if (!action) {
throw new Error('No action provided');
}
let status = 200;
let result;
switch (action) {
case CartForm.ACTIONS.LinesAdd:
result = await cart.addLines(inputs.lines);
break;
case CartForm.ACTIONS.LinesUpdate:
result = await cart.updateLines(inputs.lines);
break;
case CartForm.ACTIONS.LinesRemove:
result = await cart.removeLines(inputs.lineIds);
break;
case CartForm.ACTIONS.DiscountCodesUpdate: {
const formDiscountCode = inputs.discountCode;
// User inputted discount code
const discountCodes = formDiscountCode ? [formDiscountCode] : [];
// Combine discount codes already applied on cart
discountCodes.push(...inputs.discountCodes);
result = await cart.updateDiscountCodes(discountCodes);
break;
}
case CartForm.ACTIONS.GiftCardCodesAdd: {
const formGiftCardCode = inputs.giftCardCode;
const giftCardCodes = formGiftCardCode ? [formGiftCardCode] : [];
result = await cart.addGiftCardCodes(giftCardCodes);
break;
}
case CartForm.ACTIONS.GiftCardCodesRemove: {
const appliedGiftCardIds = inputs.giftCardCodes;
result = await cart.removeGiftCardCodes(appliedGiftCardIds);
break;
}
case CartForm.ACTIONS.BuyerIdentityUpdate: {
result = await cart.updateBuyerIdentity({
...inputs.buyerIdentity,
});
break;
}
case CartForm.ACTIONS.AttributesUpdateInput: {
result = await cart.updateAttributes(inputs.attributes);
break;
}
case CartForm.ACTIONS.MetafieldsSet: {
result = await cart.setMetafields(inputs.metafields);
break;
}
default:
throw new Error(`${action} cart action is not defined`);
}
const cartId = result?.cart?.id;
const headers = cartId ? cart.setCartId(result.cart.id) : new Headers();
const { cart: cartResult, errors, warnings } = result;
const redirectTo = formData.get('redirectTo') ?? null;
if (typeof redirectTo === 'string') {
status = 303;
headers.set('Location', redirectTo);
}
return data(
{
cart: cartResult,
errors,
warnings,
analytics: {
cartId,
},
},
{ status, headers },
);
}
/**
* @param {Route.LoaderArgs}
*/
export async function loader({ context }) {
const { cart } = context;
return await cart.get();
}
export default function Cart() {
/** @type {LoaderReturnData} */
const cart = useLoaderData();
return (
<div className="tnm-cart-page">
<div className="tnm-cart-page-header">
<h1 className="tnm-cart-page-title">Shopping Cart</h1>
<p className="tnm-cart-page-count">
{cart?.totalQuantity
? `${cart.totalQuantity} item${cart.totalQuantity !== 1 ? 's' : ''} in your cart`
: 'Your cart is empty'}
</p>
</div>
<CartMain layout="page" cart={cart} />
</div>
);
}
const CART_METAFIELDS_SET_MUTATION = `#graphql
mutation CartMetafieldsSet($metafields: [CartMetafieldsSetInput!]!) {
cartMetafieldsSet(metafields: $metafields) {
userErrors {
code
elementIndex
field
message
}
}
}
`;
/** @typedef {import('react-router').HeadersFunction} HeadersFunction */
/** @typedef {import('./+types/cart').Route} Route */
/** @typedef {import('@shopify/hydrogen').CartQueryDataReturn} CartQueryDataReturn */
/** @typedef {import('@shopify/remix-oxygen').SerializeFrom<typeof loader>} LoaderReturnData */
/** @typedef {import('@shopify/remix-oxygen').SerializeFrom<typeof action>} ActionReturnData */
# caller function
fetcher.submit(
{
[CartForm.INPUT_NAME]: JSON.stringify({
action: CartForm.ACTIONS.MetafieldsSet,
inputs: {
metafields: [
{
key: 'custom.selected_city',
value: String(pending).toLowerCase(),
type: 'single_line_text_field',
},
],
},
}),
},
{method: 'POST', action: '/cart'},
);
It should update meta fields on cart without any error.
[
{
"_1": 2
},
"data",
{
"_3": 4,
"_7": 8,
"_19": -7,
"_20": 21
},
"cart",
{
"_5": 6
},
"id",
"gid://shopify/Cart/hWNAyjRAf8nFkMPdh4ouTwU0?key=e24a9e518934b9b15ca8261df7f7bc41",
"errors",
[
9,
13,
16
],
[
"SanitizedError",
10,
11,
12
],
"GraphQLError",
"[h2:error:storefront.mutation] VisitorConsent isn't a defined input type (on $visitorConsent) - Request ID: c50433a1-bb00-448c-863b-43050e081d90-1776088828",
"GraphQLError: [h2:error:storefront.mutation] VisitorConsent isn't a defined input type (on $visitorConsent) - Request ID: c50433a1-bb00-448c-863b-43050e081d90-1776088828\n at node_modules/.pnpm/@shopify+hydrogen@2026.4.0_@react-router+dev@7.12.0_@types+node@25.5.0_jiti@2.6.1_light_374ecf23fd49cb01330dcf9d7972b9ee/node_modules/@shopify/hydrogen/dist/development/index.js:29:66\n at Object.setMetafields (node_modules/.pnpm/@shopify+hydrogen@2026.4.0_@react-router+dev@7.12.0_@types+node@25.5.0_jiti@2.6.1_light_374ecf23fd49cb01330dcf9d7972b9ee/node_modules/@shopify/hydrogen/dist/development/index.js:340:55)\n at action (app/routes/cart.jsx:80:27)\n at callRouteHandler (node_modules/.vite/deps_ssr/react-router.js:11759:16)\n at node_modules/.vite/deps_ssr/react-router.js:5217:19\n at callLoaderOrAction (node_modules/.vite/deps_ssr/react-router.js:5269:16)\n at async Promise.all (index 0)\n at defaultDataStrategy (node_modules/.vite/deps_ssr/react-router.js:4885:17)",
[
"SanitizedError",
10,
14,
15
],
"[h2:error:storefront.mutation] Directive 'inContext' doesn't accept argument 'visitorConsent' - Request ID: c50433a1-bb00-448c-863b-43050e081d90-1776088828",
"GraphQLError: [h2:error:storefront.mutation] Directive 'inContext' doesn't accept argument 'visitorConsent' - Request ID: c50433a1-bb00-448c-863b-43050e081d90-1776088828\n at node_modules/.pnpm/@shopify+hydrogen@2026.4.0_@react-router+dev@7.12.0_@types+node@25.5.0_jiti@2.6.1_light_374ecf23fd49cb01330dcf9d7972b9ee/node_modules/@shopify/hydrogen/dist/development/index.js:29:66\n at Object.setMetafields (node_modules/.pnpm/@shopify+hydrogen@2026.4.0_@react-router+dev@7.12.0_@types+node@25.5.0_jiti@2.6.1_light_374ecf23fd49cb01330dcf9d7972b9ee/node_modules/@shopify/hydrogen/dist/development/index.js:340:55)\n at action (app/routes/cart.jsx:80:27)\n at callRouteHandler (node_modules/.vite/deps_ssr/react-router.js:11759:16)\n at node_modules/.vite/deps_ssr/react-router.js:5217:19\n at callLoaderOrAction (node_modules/.vite/deps_ssr/react-router.js:5269:16)\n at async Promise.all (index 0)\n at defaultDataStrategy (node_modules/.vite/deps_ssr/react-router.js:4885:17)",
[
"SanitizedError",
10,
17,
18
],
"[h2:error:storefront.mutation] Variable $visitorConsent is declared by cartMetafieldsSet but not used - Request ID: c50433a1-bb00-448c-863b-43050e081d90-1776088828",
"GraphQLError: [h2:error:storefront.mutation] Variable $visitorConsent is declared by cartMetafieldsSet but not used - Request ID: c50433a1-bb00-448c-863b-43050e081d90-1776088828\n at node_modules/.pnpm/@shopify+hydrogen@2026.4.0_@react-router+dev@7.12.0_@types+node@25.5.0_jiti@2.6.1_light_374ecf23fd49cb01330dcf9d7972b9ee/node_modules/@shopify/hydrogen/dist/development/index.js:29:66\n at Object.setMetafields (node_modules/.pnpm/@shopify+hydrogen@2026.4.0_@react-router+dev@7.12.0_@types+node@25.5.0_jiti@2.6.1_light_374ecf23fd49cb01330dcf9d7972b9ee/node_modules/@shopify/hydrogen/dist/development/index.js:340:55)\n at action (app/routes/cart.jsx:80:27)\n at callRouteHandler (node_modules/.vite/deps_ssr/react-router.js:11759:16)\n at node_modules/.vite/deps_ssr/react-router.js:5217:19\n at callLoaderOrAction (node_modules/.vite/deps_ssr/react-router.js:5269:16)\n at async Promise.all (index 0)\n at defaultDataStrategy (node_modules/.vite/deps_ssr/react-router.js:4885:17)",
"warnings",
"analytics",
{
"_22": 6
},
"cartId"
]
What is the location of your example repository?
No response
Which package or tool is having this issue?
Hydrogen
What version of that package or tool are you using?
2026.4.0
What version of React Router 7 are you using?
7.12.0
Steps to Reproduce
Expected Behavior
It should update meta fields on cart without any error.
Actual Behavior
The error throwing