# CartForm Creates a form for managing cart operations. Use `CartActionInput` to accept form inputs of known type. ```js import {json} from '@remix-run/server-runtime'; import {CartForm} from '@shopify/hydrogen'; import invariant from 'tiny-invariant'; export default function Cart() { return ( ); } 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}); } ``` ```ts import {type ActionFunctionArgs, json} from '@remix-run/server-runtime'; import { type CartQueryDataReturn, type HydrogenCart, CartForm, } from '@shopify/hydrogen'; import invariant from 'tiny-invariant'; export default function Cart() { return ( ); } export async function action({request, context}: ActionFunctionArgs) { 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: CartQueryDataReturn; 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}); } ``` ## Props ### CartAttributesUpdateProps ### action value: `"AttributesUpdateInput"` ### inputs value: `{ attributes: AttributeInput[]; } & OtherFormData` ### CartBuyerIdentityUpdateProps ### action value: `"BuyerIdentityUpdate"` ### inputs value: `{ buyerIdentity: CartBuyerIdentityInput; } & OtherFormData` ### CartCreateProps ### action value: `"Create"` ### inputs value: `{ input: CartInput; } & OtherFormData` ### CartDiscountCodesUpdateProps ### action value: `"DiscountCodesUpdate"` ### inputs value: `{ discountCodes: string[]; } & OtherFormData` ### CartLinesAddProps ### action value: `"LinesAdd"` ### inputs value: `{ lines: CartLineInput[]; } & OtherFormData` ### CartLinesUpdateProps ### action value: `"LinesUpdate"` ### inputs value: `{ lines: CartLineUpdateInput[]; } & OtherFormData` ### CartLinesRemoveProps ### action value: `"LinesRemove"` ### inputs value: `{ lineIds: string[]; } & OtherFormData` ### CartNoteUpdateProps ### action value: `"NoteUpdate"` ### inputs value: `{ note: string; } & OtherFormData` ### CartSelectedDeliveryOptionsUpdateProps ### action value: `"SelectedDeliveryOptionsUpdate"` ### inputs value: `{ selectedDeliveryOptions: CartSelectedDeliveryOptionInput[]; } & OtherFormData` ### CartMetafieldsSetProps ### action value: `"MetafieldsSet"` ### inputs value: `{ metafields: MetafieldWithoutOwnerId[]; } & OtherFormData` ### CartMetafieldDeleteProps ### action value: `"MetafieldsDelete"` ### inputs value: `{ key: string; } & OtherFormData` ### CartCustomProps ### action value: ``Custom${string}`` ### inputs value: `Record` ### CartFormCommonProps ### children value: `ReactNode | ((fetcher: FetcherWithComponents) => ReactNode)` Children nodes of CartForm. Children can be a render prop that receives the fetcher. ### route value: `string` The route to submit the form to. Defaults to the current route. ### fetcherKey value: `string` Optional key to use for the fetcher. ## Examples Creates a form for managing cart operations. Use `CartActionInput` to accept form inputs of known type. ### CartForm using HTML input tags as form inputs ```jsx import {json} from '@remix-run/server-runtime'; import {CartForm} from '@shopify/hydrogen'; import invariant from 'tiny-invariant'; export default function Note() { return ( ); } 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}); } ``` ```tsx import {type ActionFunctionArgs, json} from '@remix-run/server-runtime'; import { type CartQueryDataReturn, type HydrogenCart, CartForm, } from '@shopify/hydrogen'; import invariant from 'tiny-invariant'; export default function Note() { return ( ); } export async function action({request, context}: ActionFunctionArgs) { 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: CartQueryDataReturn; 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}); } ``` ### Custom actions ```jsx import {json} from '@remix-run/server-runtime'; import {CartForm} from '@shopify/hydrogen'; import invariant from 'tiny-invariant'; export default function Cart() { return ( ); } 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}); } ``` ```tsx import {type ActionFunctionArgs, json} from '@remix-run/server-runtime'; import { type CartQueryDataReturn, 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 ( ); } export async function action({request, context}: ActionFunctionArgs) { 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: CartQueryDataReturn; 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}); } ``` ### CartForm with fetcher ```jsx 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 (
{ fetcher.submit( { [CartForm.INPUT_NAME]: JSON.stringify(buildFormInput(event)), }, {method: 'POST', action: '/cart'}, ); }} />
); } 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}); } ``` ```tsx import {useFetcher} from '@remix-run/react'; import {type ActionFunctionArgs, json} from '@remix-run/server-runtime'; import { type CartQueryDataReturn, 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, ) => CartActionInput = (event) => ({ action: CartForm.ACTIONS.MetafieldsSet, inputs: { metafields: [ { key: 'custom.gift', type: 'boolean', value: event.target.checked.toString(), }, ], }, }); return (
{ fetcher.submit( { [CartForm.INPUT_NAME]: JSON.stringify(buildFormInput(event)), }, {method: 'POST', action: '/cart'}, ); }} />
); } export async function action({request, context}: ActionFunctionArgs) { 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: CartQueryDataReturn; 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}); } ```