Dedicated to the Hydrogen framework, headless commerce, and building custom storefronts using the Storefront API.
I'm building a Hydrogen store. I'm running into a storefront api error attempting to update a product's quantity with Remix's useFetcher. My code looks like this:
import {useState} from 'react';
import {Listbox} from '@headlessui/react';
import {CartForm} from '@shopify/hydrogen';
import {useFetcher} from '@remix-run/react';
export default function QuantityPicker({line}) {
if (!line || typeof line?.quantity === 'undefined') return null;
const fetcher = useFetcher();
const {id: lineId, quantity, attributes, merchandise} = line;
const {id: merchId} = merchandise;
const [selected, setSelected] = useState(quantity);
const buildFormInput = (qty) => ({
action: CartForm.ACTIONS.LinesUpdate,
inputs: [
{
id: lineId,
merchandiseId: merchId,
quantity: qty,
attributes: attributes,
},
],
});
return (
<Listbox
value={selected}
onChange={(qty) => {
if (qty === selected) {
return;
}
fetcher.submit(
{
[CartForm.INPUT_NAME]: JSON.stringify(buildFormInput(qty)),
},
{method: 'POST', action: '/cart'},
);
setSelected(qty);
}}
>
{/* listbox component options */}
</Listbox>
);
}
And this is the error I'm getting:
[h2:error:storefront.mutation] Variable $lines of type [CartLineUpdateInput!]! was provided invalid value - Request ID: 50a9b037-4794-4489-9b90-53981xxxxxxx
Can anyone tell what I might be getting wrong? I've gotten this to work in GraphiQL, but struggling to get it working in my store code.
Thanks for any suggestions
Solved! Go to the solution
This is an accepted solution.
Thanks for the nudge in the right direction Scott, I'm new to GraphQL and though it seems obvious now, it hadn't occurred to me that the CartLinesUpdate mutation was sort of spelling out the shape of the inputs it needed.
mutation cartLinesUpdate(
$cartId: ID!
$lines: [CartLineUpdateInput!]!
$language: LanguageCode
$country: CountryCode
) @inContext(country: null, language: null) {
...
}
I updated the shape of the inputs object that I am passing, and it's working now. Here's what it looks like now:
import {useState} from 'react';
import {Listbox} from '@headlessui/react';
import {CartForm} from '@shopify/hydrogen';
import {useFetcher} from '@remix-run/react';
export default function QuantityPicker({line}) {
if (!line || typeof line?.quantity === 'undefined') return null;
const fetcher = useFetcher();
const {id: lineId, quantity, attributes, merchandise} = line;
const {id: merchId} = merchandise;
const [selected, setSelected] = useState(quantity);
const buildFormInput = (qty) => ({
action: CartForm.ACTIONS.LinesUpdate,
inputs: {
lines: [
{
attributes: attributes,
id: lineId,
merchandiseId: merchId,
quantity: qty,
},
],
},
});
return (
<Listbox
value={selected}
onChange={(qty) => {
if (qty === selected) {
return;
}
fetcher.submit(
{
[CartForm.INPUT_NAME]: JSON.stringify(buildFormInput(qty)),
},
{method: 'POST', action: '/cart'},
);
setSelected(qty);
}}
>
{/* rest of listbox component */}
</Listbox>
);
}
Hey @ason-dev
What do you see when you log this piece?
{
action: CartForm.ACTIONS.LinesUpdate,
inputs: [
{
id: lineId,
merchandiseId: merchId,
quantity: qty,
attributes: attributes,
},
],
}
Are you able to provide a full request ID? I can take a look at the request in the logs.
Scott | Developer Advocate @ Shopify
Hi Scott, here's the output of logging that:
{
"action":"LinesUpdate",
"inputs":[{
"id":"gid://shopify/CartLine/525cb323-9429-4094-8ec0-2d54ef1481d0?cart=c1-24d8df314b88afecf86410da54b9f9ce",
"merchandiseId":"gid://shopify/ProductVariant/46243164225840",
"quantity":2,
"attributes":[{"key":"Case engraving","value":"John Doe"}]
}]
}
And here's a full error message with a request ID:
[h2:error:storefront.mutation] Variable $lines of type [CartLineUpdateInput!]! was provided invalid value - Request ID: af30a7d0-4bb4-472e-a65c-5e9198f2daec
Thanks for looking into this!
Thanks @ason-dev - looking ok so far. Can I trouble you for one more request ID please? Our log retention period is only three days and I've just come back to this. Sorry and thanks!
Scott | Developer Advocate @ Shopify
No problem, here's a fresh one.
[h2:error:storefront.mutation] Variable $lines of type [CartLineUpdateInput!]! was provided invalid value - Request ID: ec2d588f-b621-40c8-b065-69ebda63aea2
Thanks @ason-dev! On our end, the mutation/variables look like this:
mutation cartLinesUpdate(
$cartId: ID!
$lines: [CartLineUpdateInput!]!
$language: LanguageCode
$country: CountryCode
) @inContext(country: null, language: null) {
cartLinesUpdate(cartId: null, lines: null) {
cart {
...CartApiMutation
}
errors: userErrors {
...CartApiError
}
}
}
fragment CartApiMutation on Cart {
id
totalQuantity
}
fragment CartApiError on CartUserError {
message
field
code
}
// Variables:
{
cartId: "gid://shopify/Cart/c1-24d8df314b88afecf86410da54b9f9ce",
country: "US",
language: "EN",
}
Note the `lines` variable is missing.
Hope this helps you debug. If you get stuck I'm happy to take a look at your application code.
Scott | Developer Advocate @ Shopify
This is an accepted solution.
Thanks for the nudge in the right direction Scott, I'm new to GraphQL and though it seems obvious now, it hadn't occurred to me that the CartLinesUpdate mutation was sort of spelling out the shape of the inputs it needed.
mutation cartLinesUpdate(
$cartId: ID!
$lines: [CartLineUpdateInput!]!
$language: LanguageCode
$country: CountryCode
) @inContext(country: null, language: null) {
...
}
I updated the shape of the inputs object that I am passing, and it's working now. Here's what it looks like now:
import {useState} from 'react';
import {Listbox} from '@headlessui/react';
import {CartForm} from '@shopify/hydrogen';
import {useFetcher} from '@remix-run/react';
export default function QuantityPicker({line}) {
if (!line || typeof line?.quantity === 'undefined') return null;
const fetcher = useFetcher();
const {id: lineId, quantity, attributes, merchandise} = line;
const {id: merchId} = merchandise;
const [selected, setSelected] = useState(quantity);
const buildFormInput = (qty) => ({
action: CartForm.ACTIONS.LinesUpdate,
inputs: {
lines: [
{
attributes: attributes,
id: lineId,
merchandiseId: merchId,
quantity: qty,
},
],
},
});
return (
<Listbox
value={selected}
onChange={(qty) => {
if (qty === selected) {
return;
}
fetcher.submit(
{
[CartForm.INPUT_NAME]: JSON.stringify(buildFormInput(qty)),
},
{method: 'POST', action: '/cart'},
);
setSelected(qty);
}}
>
{/* rest of listbox component */}
</Listbox>
);
}