--- title: CartForm description: Creates a form for managing cart operations. Use `CartActionInput` to accept form inputs of known type. api_version: 2023-07 api_name: hydrogen source_url: html: https://shopify.dev/docs/api/hydrogen/2023-07/components/cartform md: https://shopify.dev/docs/api/hydrogen/2023-07/components/cartform.md --- # Cart​Formcomponent Creates a form for managing cart operations. Use `CartActionInput` to accept form inputs of known type. ## Props `CartActionInputProps & CartFormCommonProps` ### CartActionInputProps * CartAttributesUpdateProps | CartBuyerIdentityUpdateProps | CartCreateProps | CartDiscountCodesUpdateProps | CartLinesAddProps | CartLinesUpdateProps | CartLinesRemoveProps | CartNoteUpdateProps | CartSelectedDeliveryOptionsUpdateProps | CartMetafieldsSetProps | CartMetafieldDeleteProps | CartCustomProps ### CartFormCommonProps * children any required Children nodes of CartForm. Children can be a render prop that receives the fetcher. * route string The route to submit the form to. Defaults to the current route. ### CartActionInputProps ```ts CartAttributesUpdateProps | CartBuyerIdentityUpdateProps | CartCreateProps | CartDiscountCodesUpdateProps | CartLinesAddProps | CartLinesUpdateProps | CartLinesRemoveProps | CartNoteUpdateProps | CartSelectedDeliveryOptionsUpdateProps | CartMetafieldsSetProps | CartMetafieldDeleteProps | CartCustomProps ``` ### CartAttributesUpdateProps * action ```ts "AttributesUpdateInput" ``` * inputs ```ts { attributes: AttributeInput[]; } & OtherFormData ``` ```ts { action: 'AttributesUpdateInput'; inputs?: { attributes: AttributeInput[]; } & OtherFormData; } ``` ### AttributeInput ```ts AttributeInput ``` ### OtherFormData ```ts OtherFormData ``` ### CartBuyerIdentityUpdateProps * action ```ts "BuyerIdentityUpdate" ``` * inputs ```ts { buyerIdentity: CartBuyerIdentityInput; } & OtherFormData ``` ```ts { action: 'BuyerIdentityUpdate'; inputs?: { buyerIdentity: CartBuyerIdentityInput; } & OtherFormData; } ``` ### CartBuyerIdentityInput ```ts CartBuyerIdentityInput ``` ### CartCreateProps * action ```ts "Create" ``` * inputs ```ts { input: CartInput; } & OtherFormData ``` ```ts { action: 'Create'; inputs?: { input: CartInput; } & OtherFormData; } ``` ### CartInput ```ts CartInput ``` ### CartDiscountCodesUpdateProps * action ```ts "DiscountCodesUpdate" ``` * inputs ```ts { discountCodes: string[]; } & OtherFormData ``` ```ts { action: 'DiscountCodesUpdate'; inputs?: { discountCodes: string[]; } & OtherFormData; } ``` ### CartLinesAddProps * action ```ts "LinesAdd" ``` * inputs ```ts { lines: CartLineInput[]; } & OtherFormData ``` ```ts { action: 'LinesAdd'; inputs?: { lines: CartLineInput[]; } & OtherFormData; } ``` ### CartLineInput ```ts CartLineInput ``` ### CartLinesUpdateProps * action ```ts "LinesUpdate" ``` * inputs ```ts { lines: CartLineUpdateInput[]; } & OtherFormData ``` ```ts { action: 'LinesUpdate'; inputs?: { lines: CartLineUpdateInput[]; } & OtherFormData; } ``` ### CartLineUpdateInput ```ts CartLineUpdateInput ``` ### CartLinesRemoveProps * action ```ts "LinesRemove" ``` * inputs ```ts { lineIds: string[]; } & OtherFormData ``` ```ts { action: 'LinesRemove'; inputs?: { lineIds: string[]; } & OtherFormData; } ``` ### CartNoteUpdateProps * action ```ts "NoteUpdate" ``` * inputs ```ts { note: string; } & OtherFormData ``` ```ts { action: 'NoteUpdate'; inputs?: { note: string; } & OtherFormData; } ``` ### CartSelectedDeliveryOptionsUpdateProps * action ```ts "SelectedDeliveryOptionsUpdate" ``` * inputs ```ts { selectedDeliveryOptions: CartSelectedDeliveryOptionInput[]; } & OtherFormData ``` ```ts { action: 'SelectedDeliveryOptionsUpdate'; inputs?: { selectedDeliveryOptions: CartSelectedDeliveryOptionInput[]; } & OtherFormData; } ``` ### CartSelectedDeliveryOptionInput ```ts CartSelectedDeliveryOptionInput ``` ### CartMetafieldsSetProps * action ```ts "MetafieldsSet" ``` * inputs ```ts { metafields: MetafieldWithoutOwnerId[]; } & OtherFormData ``` ```ts { action: 'MetafieldsSet'; inputs?: { metafields: MetafieldWithoutOwnerId[]; } & OtherFormData; } ``` ### MetafieldWithoutOwnerId ```ts MetafieldWithoutOwnerId ``` ### CartMetafieldDeleteProps * action ```ts "MetafieldsDelete" ``` * inputs ```ts { key: string; } & OtherFormData ``` ```ts { action: 'MetafieldsDelete'; inputs?: { key: Scalars['String']; } & OtherFormData; } ``` ### CartCustomProps * action ```ts `Custom${string}` ``` * inputs ```ts Record ``` ```ts { action: `Custom${string}`; inputs?: Record; } ``` ### CartFormCommonProps * children Children nodes of CartForm. Children can be a render prop that receives the fetcher. ```ts any ``` * route The route to submit the form to. Defaults to the current route. ```ts string ``` ```ts { /** * Children nodes of CartForm. * Children can be a render prop that receives the fetcher. */ children: | React.ReactNode | ((fetcher: FetcherWithComponents) => 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 ```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}); } ``` ##### TypeScript ```ts 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 ( ); } 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}); } ``` ## Examples Examples of various ways to use the `CartForm` component. ### CartForm using HTML input tags as form inputs Example Use HTML input tags with CartForm to accept form inputs. ### Examples * #### Example ##### Description Use HTML input tags with CartForm to accept form inputs. ##### JavaScript ```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}); } ``` ##### TypeScript ```tsx 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 ( ); } 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}); } ``` ### Custom actions Example Create custom actions to accept form inputs of unknown type. Just prepend `Custom` in front of your custom action name. ### Examples * #### Example ##### Description Create custom actions to accept form inputs of unknown type. Just prepend \`Custom\` in front of your custom action name. ##### JavaScript ```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}); } ``` ##### TypeScript ```tsx 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 ( ); } 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}); } ``` ### CartForm with fetcher Example 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. ### Examples * #### 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 ```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}); } ``` ##### TypeScript ```tsx 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, ) => 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}: 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}); } ```