# 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 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}); } ``` ## Props ### CartAttributesUpdateProps ### action value: `"AttributesUpdateInput"` ### inputs value: `{ attributes: AttributeInput[]; } & OtherFormData` - AttributeInput: AttributeInput - OtherFormData: OtherFormData ### CartBuyerIdentityUpdateProps ### action value: `"BuyerIdentityUpdate"` ### inputs value: `{ buyerIdentity: CartBuyerIdentityInput; } & OtherFormData` - OtherFormData: OtherFormData - CartBuyerIdentityInput: CartBuyerIdentityInput ### CartCreateProps ### action value: `"Create"` ### inputs value: `{ input: CartInput; } & OtherFormData` - OtherFormData: OtherFormData - CartInput: CartInput ### CartDiscountCodesUpdateProps ### action value: `"DiscountCodesUpdate"` ### inputs value: `{ discountCodes: string[]; } & OtherFormData` - OtherFormData: OtherFormData ### CartLinesAddProps ### action value: `"LinesAdd"` ### inputs value: `{ lines: CartLineInput[]; } & OtherFormData` - OtherFormData: OtherFormData - CartLineInput: CartLineInput ### CartLinesUpdateProps ### action value: `"LinesUpdate"` ### inputs value: `{ lines: CartLineUpdateInput[]; } & OtherFormData` - OtherFormData: OtherFormData - CartLineUpdateInput: CartLineUpdateInput ### CartLinesRemoveProps ### action value: `"LinesRemove"` ### inputs value: `{ lineIds: string[]; } & OtherFormData` - OtherFormData: OtherFormData ### CartNoteUpdateProps ### action value: `"NoteUpdate"` ### inputs value: `{ note: string; } & OtherFormData` - OtherFormData: OtherFormData ### CartSelectedDeliveryOptionsUpdateProps ### action value: `"SelectedDeliveryOptionsUpdate"` ### inputs value: `{ selectedDeliveryOptions: CartSelectedDeliveryOptionInput[]; } & OtherFormData` - OtherFormData: OtherFormData - CartSelectedDeliveryOptionInput: CartSelectedDeliveryOptionInput ### CartMetafieldsSetProps ### action value: `"MetafieldsSet"` ### inputs value: `{ metafields: MetafieldWithoutOwnerId[]; } & OtherFormData` - OtherFormData: OtherFormData - MetafieldWithoutOwnerId: MetafieldWithoutOwnerId ### CartMetafieldDeleteProps ### action value: `"MetafieldsDelete"` ### inputs value: `{ key: string; } & OtherFormData` - OtherFormData: OtherFormData ### CartCustomProps ### action value: ``Custom${string}`` ### inputs value: `Record` ### CartFormCommonProps ### children value: `any` 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. ## 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 Use HTML input tags with CartForm to accept 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 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 Create custom actions to accept form inputs of unknown type. Just prepend `Custom` in front of your custom action name.```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 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 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.```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 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}); } ``` ## 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 Use HTML input tags with CartForm to accept 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 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 Create custom actions to accept form inputs of unknown type. Just prepend `Custom` in front of your custom action name.```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 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 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.```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 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}); } ```