Cart Form
Creates a form for managing cart operations. Use to accept form inputs of known type.
Anchor to propsProps
CartActionInputProps
CartFormCommonProps
- Anchor to childrenchildrenanyrequired
Children nodes of CartForm. Children can be a render prop that receives the fetcher.
- Anchor to routeroutestring
The route to submit the form to. Defaults to the current route.
CartActionInputProps
CartAttributesUpdateProps | CartBuyerIdentityUpdateProps | CartCreateProps | CartDiscountCodesUpdateProps | CartLinesAddProps | CartLinesUpdateProps | CartLinesRemoveProps | CartNoteUpdateProps | CartSelectedDeliveryOptionsUpdateProps | CartMetafieldsSetProps | CartMetafieldDeleteProps | CartCustomPropsCartAttributesUpdateProps
- action
"AttributesUpdateInput" - inputs
{ attributes: AttributeInput[]; } & OtherFormData
{
action: 'AttributesUpdateInput';
inputs?: {
attributes: AttributeInput[];
} & OtherFormData;
}AttributeInput
AttributeInputOtherFormData
OtherFormDataCartBuyerIdentityUpdateProps
- action
"BuyerIdentityUpdate" - inputs
{ buyerIdentity: CartBuyerIdentityInput; } & OtherFormData
{
action: 'BuyerIdentityUpdate';
inputs?: {
buyerIdentity: CartBuyerIdentityInput;
} & OtherFormData;
}CartBuyerIdentityInput
CartBuyerIdentityInputCartCreateProps
- action
"Create" - inputs
{ input: CartInput; } & OtherFormData
{
action: 'Create';
inputs?: {
input: CartInput;
} & OtherFormData;
}CartInput
CartInputCartDiscountCodesUpdateProps
- action
"DiscountCodesUpdate" - inputs
{ discountCodes: string[]; } & OtherFormData
{
action: 'DiscountCodesUpdate';
inputs?: {
discountCodes: string[];
} & OtherFormData;
}CartLinesAddProps
- action
"LinesAdd" - inputs
{ lines: CartLineInput[]; } & OtherFormData
{
action: 'LinesAdd';
inputs?: {
lines: CartLineInput[];
} & OtherFormData;
}CartLineInput
CartLineInputCartLinesUpdateProps
- action
"LinesUpdate" - inputs
{ lines: CartLineUpdateInput[]; } & OtherFormData
{
action: 'LinesUpdate';
inputs?: {
lines: CartLineUpdateInput[];
} & OtherFormData;
}CartLineUpdateInput
CartLineUpdateInputCartLinesRemoveProps
- action
"LinesRemove" - inputs
{ lineIds: string[]; } & OtherFormData
{
action: 'LinesRemove';
inputs?: {
lineIds: string[];
} & OtherFormData;
}CartNoteUpdateProps
- action
"NoteUpdate" - inputs
{ note: string; } & OtherFormData
{
action: 'NoteUpdate';
inputs?: {
note: string;
} & OtherFormData;
}CartSelectedDeliveryOptionsUpdateProps
- action
"SelectedDeliveryOptionsUpdate" - inputs
{ selectedDeliveryOptions: CartSelectedDeliveryOptionInput[]; } & OtherFormData
{
action: 'SelectedDeliveryOptionsUpdate';
inputs?: {
selectedDeliveryOptions: CartSelectedDeliveryOptionInput[];
} & OtherFormData;
}CartSelectedDeliveryOptionInput
CartSelectedDeliveryOptionInputCartMetafieldsSetProps
- action
"MetafieldsSet" - inputs
{ metafields: MetafieldWithoutOwnerId[]; } & OtherFormData
{
action: 'MetafieldsSet';
inputs?: {
metafields: MetafieldWithoutOwnerId[];
} & OtherFormData;
}MetafieldWithoutOwnerId
MetafieldWithoutOwnerIdCartMetafieldDeleteProps
- action
"MetafieldsDelete" - inputs
{ key: string; } & OtherFormData
{
action: 'MetafieldsDelete';
inputs?: {
key: Scalars['String'];
} & OtherFormData;
}CartCustomProps
- action
`Custom${string}` - inputs
Record<string, unknown>
{
action: `Custom${string}`;
inputs?: Record<string, unknown>;
}CartFormCommonProps
- children
Children nodes of CartForm. Children can be a render prop that receives the fetcher.
any - route
The route to submit the form to. Defaults to the current route.
string
{
/**
* Children nodes of CartForm.
* Children can be a render prop that receives the fetcher.
*/
children:
| React.ReactNode
| ((fetcher: FetcherWithComponents<any>) => React.ReactNode);
/**
* The route to submit the form to. Defaults to the current route.
*/
route?: string;
}Examples
example
Description
This is the default example
JavaScript
import {json} from '@remix-run/server-runtime'; import {CartForm} from '@shopify/hydrogen'; import invariant from 'tiny-invariant'; export default function Cart() { return ( <CartForm action={CartForm.ACTIONS.LinesUpdate} inputs={{ lines: [ { id: 'gid://shopify/CartLine/123456789', quantity: 3, }, ], other: 'data', }} > <button>Quantity up</button> </CartForm> ); } export async function action({request, context}) { const {cart} = context; const formData = await request.formData(); const {action, inputs} = CartForm.getFormInput(formData); let status = 200; let result; if (action === CartForm.ACTIONS.LinesUpdate) { result = await cart.updateLines(inputs.lines); } else { invariant(false, `${action} cart action is not defined`); } const headers = cart.setCartId(result.cart.id); return json(result, {status, headers}); }TypeScript
import {type ActionArgs, json} from '@remix-run/server-runtime'; import { type CartQueryData, type HydrogenCart, CartForm, } from '@shopify/hydrogen'; import invariant from 'tiny-invariant'; export default function Cart() { return ( <CartForm action={CartForm.ACTIONS.LinesUpdate} inputs={{ lines: [ { id: 'gid://shopify/CartLine/123456789', quantity: 3, }, ], other: 'data', }} > <button>Quantity up</button> </CartForm> ); } export async function action({request, context}: ActionArgs) { const cart = context.cart as HydrogenCart; // cart is type HydrogenCart or HydrogenCartCustom // Declare cart type in remix.env.d.ts for interface AppLoadContext to avoid type casting // const {cart} = context; const formData = await request.formData(); const {action, inputs} = CartForm.getFormInput(formData); let status = 200; let result: CartQueryData; if (action === CartForm.ACTIONS.LinesUpdate) { result = await cart.updateLines(inputs.lines); } else { invariant(false, `${action} cart action is not defined`); } const headers = cart.setCartId(result.cart.id); return json(result, {status, headers}); }Example
Description
Use HTML input tags with CartForm to accept form inputs.
JavaScript
import {json} from '@remix-run/server-runtime'; import {CartForm} from '@shopify/hydrogen'; import invariant from 'tiny-invariant'; export default function Note() { return ( <CartForm action={CartForm.ACTIONS.NoteUpdate}> <input type="text" name="note" /> <button>Update Note</button> </CartForm> ); } export async function action({request, context}) { const cart = context.cart; const formData = await request.formData(); const {action, inputs} = CartForm.getFormInput(formData); let status = 200; let result; if (action === CartForm.ACTIONS.NoteUpdate) { result = await cart.updateNote(inputs.note); } else { invariant(false, `${action} cart action is not defined`); } const headers = cart.setCartId(result.cart.id); return json(result, {status, headers}); }TypeScript
import {type ActionArgs, json} from '@remix-run/server-runtime'; import { type CartQueryData, type HydrogenCart, CartForm, } from '@shopify/hydrogen'; import invariant from 'tiny-invariant'; export default function Note() { return ( <CartForm action={CartForm.ACTIONS.NoteUpdate}> <input type="text" name="note" /> <button>Update Note</button> </CartForm> ); } export async function action({request, context}: ActionArgs) { const cart = context.cart as HydrogenCart; // cart is type HydrogenCart or HydrogenCartCustom // Declare cart type in remix.env.d.ts for interface AppLoadContext to avoid type casting // const {cart} = context; const formData = await request.formData(); const {action, inputs} = CartForm.getFormInput(formData); let status = 200; let result: CartQueryData; if (action === CartForm.ACTIONS.NoteUpdate) { result = await cart.updateNote(inputs.note); } else { invariant(false, `${action} cart action is not defined`); } const headers = cart.setCartId(result.cart.id); return json(result, {status, headers}); }Example
Description
Create custom actions to accept form inputs of unknown type. Just prepend `Custom` in front of your custom action name.
JavaScript
import {json} from '@remix-run/server-runtime'; import {CartForm} from '@shopify/hydrogen'; import invariant from 'tiny-invariant'; export default function Cart() { return ( <CartForm action="CustomEditInPlace" inputs={{ addLines: [ { merchandiseId: 'gid://shopify/Product/123456789', quantity: 1, }, ], removeLines: ['gid://shopify/CartLine/123456789'], }} > <button>Green color swatch</button> </CartForm> ); } export async function action({request, context}) { const {cart} = context; const formData = await request.formData(); const {action, inputs} = CartForm.getFormInput(formData); let status = 200; let result; if (action === 'CustomEditInPlace') { result = await cart.addLines(inputs.addLines); result = await cart.removeLines(inputs.removeLines); } else { invariant(false, `${action} cart action is not defined`); } const headers = cart.setCartId(result.cart.id); return json(result, {status, headers}); }TypeScript
import {type ActionArgs, json} from '@remix-run/server-runtime'; import { type CartQueryData, type HydrogenCart, CartForm, } from '@shopify/hydrogen'; import {type CartLineInput} from '@shopify/hydrogen-react/storefront-api-types'; import invariant from 'tiny-invariant'; export default function Cart() { return ( <CartForm action="CustomEditInPlace" inputs={{ addLines: [ { merchandiseId: 'gid://shopify/Product/123456789', quantity: 1, }, ], removeLines: ['gid://shopify/CartLine/123456789'], }} > <button>Green color swatch</button> </CartForm> ); } export async function action({request, context}: ActionArgs) { const cart = context.cart as HydrogenCart; // cart is type HydrogenCart or HydrogenCartCustom // Declare cart type in remix.env.d.ts for interface AppLoadContext to avoid type casting // const {cart} = context; const formData = await request.formData(); const {action, inputs} = CartForm.getFormInput(formData); let status = 200; let result: CartQueryData; if (action === 'CustomEditInPlace') { result = await cart.addLines(inputs.addLines as CartLineInput[]); result = await cart.removeLines(inputs.removeLines as string[]); } else { invariant(false, `${action} cart action is not defined`); } const headers = cart.setCartId(result.cart.id); return json(result, {status, headers}); }Example
Description
Use `CartForm` with a fetcher to manually submit the form. An example usage is to submit the form on changes to the state of a checkbox. When using fetcher to submit, make sure to have a `CartForm.INPUT_NAME` data key and its data should be a JSON stringify object.
JavaScript
import {useFetcher} from '@remix-run/react'; import {json} from '@remix-run/server-runtime'; import {CartForm} from '@shopify/hydrogen'; import invariant from 'tiny-invariant'; export function ThisIsGift({metafield}) { const fetcher = useFetcher(); const buildFormInput = (event) => ({ action: CartForm.ACTIONS.MetafieldsSet, inputs: { metafields: [ { key: 'custom.gift', type: 'boolean', value: event.target.checked.toString(), }, ], }, }); return ( <div> <input checked={metafield?.value === 'true'} type="checkbox" id="isGift" onChange={(event) => { fetcher.submit( { [CartForm.INPUT_NAME]: JSON.stringify(buildFormInput(event)), }, {method: 'POST', action: '/cart'}, ); }} /> <label htmlFor="isGift">This is a gift</label> </div> ); } export async function action({request, context}) { const {cart} = context; const formData = await request.formData(); const {action, inputs} = CartForm.getFormInput(formData); let status = 200; let result; if (action === CartForm.ACTIONS.MetafieldsSet) { result = await cart.setMetafields(inputs.metafields); } else { invariant(false, `${action} cart action is not defined`); } const headers = cart.setCartId(result.cart.id); return json(result, {status, headers}); }TypeScript
import {useFetcher} from '@remix-run/react'; import {type ActionArgs, json} from '@remix-run/server-runtime'; import { type CartQueryData, type HydrogenCart, CartForm, type CartActionInput, } from '@shopify/hydrogen'; import invariant from 'tiny-invariant'; import type {Cart} from '@shopify/hydrogen/storefront-api-types'; export function ThisIsGift({metafield}: {metafield: Cart['metafield']}) { const fetcher = useFetcher(); const buildFormInput: ( event: React.ChangeEvent<HTMLInputElement>, ) => CartActionInput = (event) => ({ action: CartForm.ACTIONS.MetafieldsSet, inputs: { metafields: [ { key: 'custom.gift', type: 'boolean', value: event.target.checked.toString(), }, ], }, }); return ( <div> <input checked={metafield?.value === 'true'} type="checkbox" id="isGift" onChange={(event) => { fetcher.submit( { [CartForm.INPUT_NAME]: JSON.stringify(buildFormInput(event)), }, {method: 'POST', action: '/cart'}, ); }} /> <label htmlFor="isGift">This is a gift</label> </div> ); } export async function action({request, context}: ActionArgs) { const cart = context.cart as HydrogenCart; // cart is type HydrogenCart or HydrogenCartCustom // Declare cart type in remix.env.d.ts for interface AppLoadContext to avoid type casting // const {cart} = context; const formData = await request.formData(); const {action, inputs} = CartForm.getFormInput(formData); let status = 200; let result: CartQueryData; if (action === CartForm.ACTIONS.MetafieldsSet) { result = await cart.setMetafields(inputs.metafields); } else { invariant(false, `${action} cart action is not defined`); } const headers = cart.setCartId(result.cart.id); return json(result, {status, headers}); }