CartItem
One line of the cart page: picture, name, prices, offers, a quantity input and a delete button that change the shopper's real cart.
import { CartItem } from '@salla.sa/twilight-theme-engine/components/cart';In plain words
The cart page shows one CartItem per product in the cart: its picture, name, price, a quantity box, the line total and an × to remove it.
Changing the quantity or pressing × goes straight to the Salla SDK, which updates the real cart. The component does not fetch the cart: you pass it one item from a cart you already read.
It is lazy, so it goes inside <Suspense>.
Signature
const CartItem: React.LazyExoticComponent<(props: {
item: CartItem; // the CartItem type from @salla.sa/twilight-theme-engine/types
isFirst?: boolean; // false: eager image for the first line
}) => JSX.Element>Try it live
import { Suspense } from 'react';
import { CartItem } from '@salla.sa/twilight-theme-engine/components/cart';
import type { Cart } from '@salla.sa/twilight-theme-engine/types';
export function CartLines({ cart, readAt }: { cart: Cart; readAt: number }) {
return (
<Suspense fallback={null}>
{/* CartItem is lazy. A new key per read: a deleted line has already removed its own form. */}
<div key={readAt}>
{cart.items.map((item, index) => (
<CartItem key={item.id} item={item} isFirst={index === 0} />
))}
</div>
</Suspense>
);
}
Example
import { Suspense } from 'react';
import { CartItem } from '@salla.sa/twilight-theme-engine/components/cart';
import type { Cart } from '@salla.sa/twilight-theme-engine/types';
export function CartLines({ cart }: { cart: Cart }) {
return (
<Suspense fallback={null}>
{cart.items.map((item, index) => (
<CartItem key={item.id} item={item} isFirst={index === 0} />
))}
</Suspense>
);
}
How it behaves
Each line is its own
<form id="item-<id>">. A quantity change callsSalla.form.onChange('cart.updateItem', event), and the SDK reads the hiddenidand thequantityinput. Do not put it inside another<form>.× calls
Salla.cart.deleteItem(item.id).Prices may be numbers or
{ amount, currency }objects; each is formatted withuseMoney().formatin its own currency. The total istotal_special_pricewhen the line hasdetailed_offers, elsetotal, or "Out of Stock" whenis_availableis false.Reading the cart: the engine's cart page gets the id with
Salla.cart.api.getCurrentCartId()in an effect (it creates a guest cart when there is none), thenuseQuery(cart.queries.detail(id))from/api/cart. The id lives in the browser, so the server render has no cart.
Gotchas
Nothing re-reads the cart after the SDK changes it: nothing in the engine subscribes to cart events or refetches
['cart', id], so line totals andCartSummarykeep the numbers the page loaded with. Refetch onSalla.cart.event.onUpdated(callback)(and remove it withoffUpdated(callback)), as the demo does.× removes the line with
document.querySelector('#item-<id>')?.remove()behind React's back. When you later re-render the list without that item, React tries to remove a form that is already gone. Render the list under a key that changes with each fresh read (the demo usesdataUpdatedAt) so React replaces the whole list instead.The component shares its name with the
CartItemtype from/types. Rename one on import:import type { CartItem as CartLine } from '@salla.sa/twilight-theme-engine/types'.
Related
The cart page sidebar: free-shipping bar, loyalty and gift widgets, totals, a coupon box and the Complete Order button.
SeoCartWidgetDraws nothing: on the cart page it pushes Google Tag Manager checkout events for each step and sets the browser tab title.
CartContextProviderA React context for a cart object: wrap part of the page once, then read the cart anywhere inside it with useCartContext().