Cart Formcomponent
Creates a form for managing cart operations. Use to accept form inputs of known type.
Anchor to propsProps
CartActionInputProps
CartFormCommonProps
- Anchor to childrenchildrenReactNode | ((fetcher: FetcherWithComponents<any>) => ReactNode)required
Children nodes of CartForm. Children can be a render prop that receives the fetcher.
- Anchor to fetcherKeyfetcherKeystring
Optional key to use for the fetcher.
- Anchor to routeroutestring
The route to submit the form to. Defaults to the current route.
CartFormProps
CartActionInputProps & CartFormCommonProps
CartActionInputProps
CartAttributesUpdateProps | CartBuyerIdentityUpdateProps | CartCreateProps | CartDiscountCodesUpdateProps | CartGiftCardCodesUpdateProps | CartLinesAddProps | CartLinesUpdateProps | CartLinesRemoveProps | CartNoteUpdateProps | CartSelectedDeliveryOptionsUpdateProps | CartMetafieldsSetProps | CartMetafieldDeleteProps | CartDeliveryAddressesAddProps | CartDeliveryAddressesRemoveProps | CartDeliveryAddressesUpdateProps | CartCustomProps
CartAttributesUpdateProps
- action
"AttributesUpdateInput"
- inputs
{ attributes: AttributeInput[]; } & OtherFormData
{
action: 'AttributesUpdateInput';
inputs?: {
attributes: AttributeInput[];
} & OtherFormData;
}
AttributeInput
AttributeInput
OtherFormData
OtherFormData
CartBuyerIdentityUpdateProps
- action
"BuyerIdentityUpdate"
- inputs
{ buyerIdentity: CartBuyerIdentityInput; } & OtherFormData
{
action: 'BuyerIdentityUpdate';
inputs?: {
buyerIdentity: CartBuyerIdentityInput;
} & OtherFormData;
}
CartBuyerIdentityInput
CartBuyerIdentityInput
CartCreateProps
- action
"Create"
- inputs
{ input: CartInput; } & OtherFormData
{
action: 'Create';
inputs?: {
input: CartInput;
} & OtherFormData;
}
CartInput
CartInput
CartDiscountCodesUpdateProps
- action
"DiscountCodesUpdate"
- inputs
{ discountCodes: string[]; } & OtherFormData
{
action: 'DiscountCodesUpdate';
inputs?: {
discountCodes: string[];
} & OtherFormData;
}
CartGiftCardCodesUpdateProps
- action
"GiftCardCodesUpdate"
- inputs
{ giftCardCodes: string[]; } & OtherFormData
{
action: 'GiftCardCodesUpdate';
inputs?: {
giftCardCodes: string[];
} & OtherFormData;
}
CartLinesAddProps
- action
"LinesAdd"
- inputs
{ lines: OptimisticCartLineInput[]; } & OtherFormData
{
action: 'LinesAdd';
inputs?: {
lines: Array<OptimisticCartLineInput>;
} & OtherFormData;
}
OptimisticCartLineInput
CartLineInput & {
selectedVariant?: unknown;
}
CartLineInput
CartLineInput
CartLinesUpdateProps
- action
"LinesUpdate"
- inputs
{ lines: CartLineUpdateInput[]; } & OtherFormData
{
action: 'LinesUpdate';
inputs?: {
lines: CartLineUpdateInput[];
} & OtherFormData;
}
CartLineUpdateInput
CartLineUpdateInput
CartLinesRemoveProps
- 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
CartSelectedDeliveryOptionInput
CartMetafieldsSetProps
- action
"MetafieldsSet"
- inputs
{ metafields: MetafieldWithoutOwnerId[]; } & OtherFormData
{
action: 'MetafieldsSet';
inputs?: {
metafields: MetafieldWithoutOwnerId[];
} & OtherFormData;
}
MetafieldWithoutOwnerId
MetafieldWithoutOwnerId
CartMetafieldDeleteProps
- action
"MetafieldsDelete"
- inputs
{ key: string; } & OtherFormData
{
action: 'MetafieldsDelete';
inputs?: {
key: Scalars['String']['input'];
} & OtherFormData;
}
CartDeliveryAddressesAddProps
- action
"DeliveryAddressesAdd"
- inputs
{ addresses: CartSelectableAddressInput[]; } & OtherFormData
{
action: 'DeliveryAddressesAdd';
inputs?: {
addresses: Array<CartSelectableAddressInput>;
} & OtherFormData;
}
CartDeliveryAddressesRemoveProps
- action
"DeliveryAddressesRemove"
- inputs
{ addressIds: string[]; } & OtherFormData
{
action: 'DeliveryAddressesRemove';
inputs?: {
addressIds: Array<string> | Array<Scalars['ID']['input']>;
} & OtherFormData;
}
CartDeliveryAddressesUpdateProps
- action
"DeliveryAddressesUpdate"
- inputs
{ addresses: CartSelectableAddressUpdateInput[]; } & OtherFormData
{
action: 'DeliveryAddressesUpdate';
inputs?: {
addresses: Array<CartSelectableAddressUpdateInput>;
} & 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.
ReactNode | ((fetcher: FetcherWithComponents<any>) => ReactNode)
- fetcherKey
Optional key to use for the fetcher.
string
- 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: ReactNode | ((fetcher: FetcherWithComponents<any>) => ReactNode);
/**
* The route to submit the form to. Defaults to the current route.
*/
route?: string;
/**
* Optional key to use for the fetcher.
* @see https://remix.run/hooks/use-fetcher#key
*/
fetcherKey?: string;
}
Example
examples
example
description
This is the default example
JavaScript
import {data} from 'react-router'; 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 data(result, {status, headers}); }
TypeScript
import {type ActionFunctionArgs, data} from 'react-router'; import { type CartQueryDataReturn, 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}: 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 data(result, {status, headers}); }
Anchor to examplesExamples
Examples of various ways to use the component.
Anchor to example-exampleExample
Use HTML input tags with CartForm to accept form inputs.
Example
examples
Example
description
Use HTML input tags with CartForm to accept form inputs.
JavaScript
import {data} from 'react-router'; 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 data(result, {status, headers}); }
TypeScript
import {type ActionFunctionArgs, data} from 'react-router'; import { type CartQueryDataReturn, 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}: 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 data(result, {status, headers}); }
Anchor to example-custom-actionsCustom actions
Anchor to example-exampleExample
Create custom actions to accept form inputs of unknown type. Just prepend Custom
in front of your custom action name.
Example
examples
Example
description
Create custom actions to accept form inputs of unknown type. Just prepend `Custom` in front of your custom action name.
JavaScript
import {data} from 'react-router'; 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 data(result, {status, headers}); }
TypeScript
import {type ActionFunctionArgs, data} from 'react-router'; 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 ( <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}: 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 data(result, {status, headers}); }
Anchor to example-cartform-with-fetcherCartForm with fetcher
Anchor to example-exampleExample
Use 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 data key and its data should be a JSON stringify object.
Example
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
import {useFetcher} from 'react-router'; import {data} from 'react-router'; 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 data(result, {status, headers}); }
TypeScript
import {useFetcher} from 'react-router'; import {type ActionFunctionArgs, data} from 'react-router'; 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<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}: 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 data(result, {status, headers}); }