Skip to main content

FavoriteListItem

Renders a single product or cluster as a horizontal row within a favorite list. Products display an inline AddToCart widget; clusters display a "View cluster" button. Each row includes an image, name, optional SKU, price, stock badge, and action buttons (add to cart, delete).

Usage

Standard favorite list page with cart integration

import FavoriteListItem from '@/components/propeller/FavoriteListItem';
import config from '@/data/config';

{items.map((item) => (
<FavoriteListItem
key={item.productId ?? item.clusterId}
item={item}
graphqlClient={graphqlClient}
user={authState.user}
cartId={cart?.cartId}
createCart={true}
onCartCreated={(newCart) => saveCart(newCart)}
afterAddToCart={(updatedCart) => saveCart(updatedCart)}
configuration={config}
onDelete={(itemId) => handleRemoveFromList(itemId)}
/>
))}

Read-only display (no actions)

<FavoriteListItem
item={product}
allowAddToCart={false}
showDelete={false}
configuration={config}
/>

SPA navigation with custom click handler

<FavoriteListItem
item={cluster}
configuration={config}
onItemClick={(item) => router.push(getClusterUrl(item))}
onDelete={(itemId) => handleRemoveFromList(itemId)}
/>

Minimal display (image + name only)

<FavoriteListItem
item={product}
showSku={false}
showStockComponent={false}
allowAddToCart={false}
showDelete={false}
/>

With stock display and custom labels

<FavoriteListItem
item={product}
showStockComponent={true}
showAvailability={true}
showStock={true}
graphqlClient={graphqlClient}
user={authState.user}
cartId={cart?.cartId}
configuration={config}
onDelete={(itemId) => handleRemoveFromList(itemId)}
labels={{
viewCluster: 'Bekijk cluster',
delete: 'Verwijderen',
inStock: 'Op voorraad',
lowStock: 'Beperkte voorraad',
outOfStock: 'Niet op voorraad',
}}
addToCartLabels={{ addToCart: 'In winkelwagen' }}
stockLabels={{ inStock: 'Op voorraad' }}
/>

With price excluding VAT

<FavoriteListItem
item={product}
includeTax={false}
graphqlClient={graphqlClient}
configuration={config}
onDelete={(itemId) => handleRemoveFromList(itemId)}
/>

Configuration

Core Props

PropTypeRequiredDefaultDescription
itemProduct | ClusterYes--The product or cluster to display
configurationobjectNo--Configuration object for URL generation (e.g., config from @/data/config). Must expose urls.getProductUrl() and urls.getClusterUrl()
classNamestringNo--Extra CSS class applied to the root element

Display Options

PropTypeRequiredDefaultDescription
titleLinkablebooleanNotrueWhether the item name and image link to the product detail page
showSkubooleanNotrueDisplay the SKU beneath the item name
showStockComponentbooleanNofalseDisplay stock availability badge
showAvailabilitybooleanNotrueShow availability status text inside ItemStock (products only)
showStockbooleanNotrueShow numeric stock quantity inside ItemStock (products only)
includeTaxbooleanNotrueInclude tax in the displayed price. When provided, overrides the internal PriceToggle localStorage state

Action Props

PropTypeRequiredDefaultDescription
allowAddToCartbooleanNotrueShow the AddToCart widget for products. Clusters always show a "View cluster" button instead
showDeletebooleanNotrueShow the delete (trash) button
onDelete(itemId: string) => voidNo--Callback when the delete button is clicked. Receives the product ID or cluster ID as a string
onItemClick(item: Product | Cluster) => voidNo--Callback when the row, item name, or image is clicked. Prevents default <a> navigation when provided

Label Overrides

PropTypeRequiredDefaultDescription
labelsRecord<string, string>No--UI string overrides for the component itself (see Labels section below)
addToCartLabelsRecord<string, string>No--Label overrides forwarded to the embedded AddToCart component
stockLabelsRecord<string, string>No--Label overrides forwarded to the embedded ItemStock component

AddToCart Pass-Through Props (products only)

These props are forwarded to the embedded AddToCart component and only take effect when the item is a product.

PropTypeRequiredDefaultDescription
graphqlClientGraphQLClientNo--Propeller SDK GraphQL client. Required for add-to-cart functionality
userContact | Customer | nullNo--Authenticated user for cart operations
cartIdstringNo--Existing cart ID to add items to
createCartbooleanNo--Auto-create a new cart if no cartId is available
onCartCreated(cart: Cart) => voidNo--Called after a new cart is created internally
onAddToCart(product, clusterId?, quantity?, childItems?, notes?, price?, showModal?) => CartNo--Fully replaces the internal CartService.addItemToCart call
afterAddToCart(cart: Cart, item?: CartMainItem) => voidNo--Called after every successful add-to-cart
showModalbooleanNofalseShow confirmation modal after a successful add
allowIncrDecrbooleanNotrueShow increment/decrement buttons beside the quantity input
enableStockValidationbooleanNofalseValidate available stock before adding to cart
languagestringNo'NL'Language code forwarded to CartService
onProceedToCheckout() => voidNo--Called when "Proceed to checkout" is clicked in the AddToCart modal

Labels

All labels are optional and fall back to English defaults:

KeyDefaultDescription
viewCluster"View cluster"Button text for cluster items
delete"Remove from list"Delete button tooltip
inStock"In stock"Stock badge for clusters with quantity > 5
lowStock"Low stock"Stock badge for clusters with quantity 1-5
outOfStock"Out of stock"Stock badge for clusters with quantity 0

Behavior

Product vs Cluster Differences

FeatureProductCluster
Image sourceproduct.media.images.items[0]cluster.defaultProduct.media.images.items[0]
Name sourceproduct.names[0].valuecluster.names[0].value, falls back to defaultProduct.names[0].value
SKU sourceproduct.skucluster.sku, falls back to defaultProduct.sku
Price sourceproduct.pricecluster.defaultProduct.price
Stock displayFull ItemStock component with availability and quantitySimplified inline badge (In stock / Low stock / Out of stock) based on defaultProduct.inventory.totalQuantity
Primary actionAddToCart widget (quantity input + add button)"View cluster" link button
DetectionHas productId propertyDoes not have productId property

Price Toggle

The component respects the global price toggle (VAT incl/excl):

  • When includeTax prop is provided, it takes precedence over any internal state.
  • When includeTax is omitted, the default is true (prices shown including VAT).
  • Price mapping from the SDK: price.net = including VAT, price.gross = excluding VAT.
  • Prices are formatted as euros with two decimal places (e.g., EUR 12.50).

Remove from List

The delete button calls onDelete(itemId) where itemId is:

  • String(product.productId) for products
  • String(cluster.clusterId) for clusters

The component does not perform the API call itself. The parent must handle the actual removal via FavoriteListService and update local state accordingly.

Add to Cart

For products, the embedded AddToCart component handles the full cart flow:

  1. If no cartId is provided and createCart is true, a new cart is created automatically.
  2. The product is added to the cart via CartService.addItemToCart.
  3. onCartCreated fires if a new cart was created; afterAddToCart fires after every successful addition.
  4. When showModal is true, a confirmation modal appears after adding.

Clusters do not support add-to-cart. They display a "View cluster" button that navigates to the cluster detail page, where the user can select a specific variant.

Click Handling

The entire row is clickable. Clicking navigates to the product/cluster detail page using the URL from configuration.urls.getProductUrl() or configuration.urls.getClusterUrl(). When onItemClick is provided, it intercepts the click and calls the callback instead, allowing SPA navigation via router.push(). The action buttons area (add to cart, delete) stops event propagation to prevent triggering the row click.

Image Fallback

When no image is available, an SVG placeholder icon is rendered instead of a broken image.

SDK Services

FavoriteListService

The component itself does not call any SDK service directly. The parent page is responsible for fetching favorite list items and handling deletion. A typical integration uses FavoriteListService to remove items:

import { FavoriteListService } from 'propeller-sdk-v2';

const favoriteListService = new FavoriteListService(graphqlClient);

async function handleRemoveFromList(itemId: string) {
await favoriteListService.removeFavoriteListItem({
favoriteListId: listId,
itemId,
});
// Re-fetch the list or optimistically remove from local state
setItems((prev) => prev.filter((i) =>
String('productId' in i ? i.productId : i.clusterId) !== itemId
));
}

CartService (via AddToCart)

When graphqlClient is provided and the item is a product, the embedded AddToCart component uses CartService internally to add items to the cart:

import { CartService } from 'propeller-sdk-v2';

const cartService = new CartService(graphqlClient);

// AddToCart handles this internally, but you can override via onAddToCart:
const updatedCart = await cartService.addItemToCart({
cartId,
productId: product.productId,
quantity: 1,
});

GraphQL Queries and Mutations

Remove item from favorite list

mutation RemoveFavoriteListItem($favoriteListId: Int!, $itemId: String!) {
favoriteListRemoveItem(
input: {
favoriteListId: $favoriteListId
itemId: $itemId
}
) {
id
name
items {
productId
clusterId
}
}
}

Add product to cart (handled by embedded AddToCart)

mutation AddToCart($cartId: String!, $productId: Int!, $quantity: Int!) {
cartAddItem(
input: {
cartId: $cartId
productId: $productId
quantity: $quantity
}
) {
cartId
items {
productId
quantity
totalPrice
}
total {
totalNet
totalGross
}
}
}