Skip to main content

FavoriteListDetails

Renders the contents of a favorite list, displaying products and clusters with client-side pagination. Each item is rendered via FavoriteListItem and supports add-to-cart, deletion, stock display, and navigation.

Usage

Full integration on a favorites detail page

import FavoriteListDetails from '@/components/propeller/FavoriteListDetails';
import { useAuth } from '@/context/AuthContext';
import { useCart } from '@/context/CartContext';
import { getGraphqlClient } from '@/lib/graphql';
import config from '@/data/config';

const graphqlClient = getGraphqlClient();
const { state: authState } = useAuth();
const { cart, saveCart } = useCart();

<FavoriteListDetails
graphqlClient={graphqlClient}
user={authState.user}
favoriteListId={listId}
onItemDelete={handleItemDelete}
onListLoaded={(list) => setPageTitle(list.name)}
configuration={config}
cartId={cart?.cartId}
createCart={true}
onCartCreated={(newCart) => saveCart(newCart)}
afterAddToCart={(updatedCart) => saveCart(updatedCart)}
itemsPerPage={12}
showPagination={true}
/>

Read-only display (no cart actions, no delete)

<FavoriteListDetails
graphqlClient={graphqlClient}
user={authState.user}
favoriteListId={listId}
allowAddToCart={false}
showDelete={false}
showPagination={false}
/>

Compact preview with limited items per page

<FavoriteListDetails
graphqlClient={graphqlClient}
user={authState.user}
favoriteListId={listId}
itemsPerPage={4}
paginationVariant="compact"
showStockComponent={true}
showSku={false}
labels={{
emptyTitle: 'No items yet',
emptyDescription: 'Start adding products to this list.',
}}
/>

Full pagination variant with custom item labels

<FavoriteListDetails
graphqlClient={graphqlClient}
user={authState.user}
favoriteListId={listId}
itemsPerPage={6}
paginationVariant="full"
itemLabels={{ deleteButton: 'Remove', viewCluster: 'View options' }}
addToCartLabels={{ addButton: 'Add to basket' }}
/>

With stock validation and checkout flow

<FavoriteListDetails
graphqlClient={graphqlClient}
user={authState.user}
favoriteListId={listId}
cartId={cart?.cartId}
createCart={true}
onCartCreated={(newCart) => saveCart(newCart)}
afterAddToCart={(updatedCart) => saveCart(updatedCart)}
showStockComponent={true}
showAvailability={true}
showStock={true}
enableStockValidation={true}
showModal={true}
onProceedToCheckout={() => router.push('/checkout')}
configuration={config}
/>

Configuration

Core

PropTypeRequiredDefaultDescription
graphqlClientGraphQLClientYes--GraphQL client instance for the Propeller SDK
userContact | CustomerYes--Logged-in user whose favorite list is displayed
favoriteListIdstringYes--ID of the favorite list to fetch
configurationobjectNo--Configuration object for URL generation (e.g., config from @/data/config)
classNamestringNo--Extra CSS class on the root element
languagestringNo'NL'Language code forwarded to the SDK for localized data
includeTaxbooleanNo--Whether prices include tax. Pass from your price context

Callbacks

PropTypeDescription
onItemDelete(itemId: string, itemType: string) => voidCalled after an item is optimistically removed. itemType is 'product' or 'cluster'. The parent should perform the actual SDK deletion
onListLoaded(list: FavoriteList) => voidCalled after the favorite list is fetched, with the full list object. Useful for setting the page title
onItemClick(item: Product | Cluster) => voidCalled when an item title or image is clicked

Item Display

PropTypeDefaultDescription
titleLinkablebooleantrueWhether item titles link to the product detail page
showStockComponentbooleanfalseShow stock availability indicator on items
showAvailabilitybooleantrueShow availability status text (e.g., "In stock") inside stock indicator
showStockbooleantrueShow numeric stock quantity inside stock indicator
showSkubooleantrueDisplay the SKU beneath item names
allowAddToCartbooleantrueEnable add-to-cart for products. Clusters show a "View cluster" link instead
showDeletebooleantrueShow the delete button on each item

Pagination

PropTypeDefaultDescription
itemsPerPagenumber12Number of items shown per page
showPaginationbooleantrueShow pagination controls below items
paginationVariantstring'compact'Pagination style: 'compact' or 'full'

Add-to-Cart (passed through to each FavoriteListItem / AddToCart)

PropTypeDefaultDescription
cartIdstring--Existing cart ID to add items to
createCartboolean--Auto-create a cart if none exists
onCartCreated(cart: Cart) => void--Called when a new cart is created internally
onAddToCart(product, clusterId?, quantity?, childItems?, notes?, price?, showModal?) => Cart--Fully replaces the internal add-to-cart call
afterAddToCart(cart: Cart, item?: CartMainItem) => void--Called after every successful add-to-cart
showModalbooleanfalseShow confirmation modal after adding to cart
allowIncrDecrbooleantrueRender increment/decrement buttons beside quantity input
enableStockValidationbooleanfalseValidate available stock before adding to cart
onProceedToCheckout() => void--Called when "Proceed to checkout" is clicked in the add-to-cart modal

Labels

PropTypeDescription
labelsRecord<string, string>UI string overrides for the component itself (see below)
addToCartLabelsRecord<string, string>Label overrides passed to AddToCart
stockLabelsRecord<string, string>Label overrides passed to ItemStock
itemLabelsRecord<string, string>Label overrides passed to FavoriteListItem

Labels

labels keys:

KeyDefault
emptyTitle"List is empty"
emptyDescription"You haven't added any products or clusters to this list yet."

Behavior

  • Data fetching: Fetches the favorite list on mount via FavoriteListService.getFavoriteList(). Re-fetches automatically whenever favoriteListId changes. Tracks the previous list ID internally to prevent duplicate fetches when the same ID is passed.
  • User-aware pricing: Price calculation variables adapt to the user type -- customerId for B2C customers, contactId and companyId for B2B contacts.
  • Client-side pagination: The SDK returns all items in a single response. The component slices the combined products + clusters array based on itemsPerPage and the current page.
  • Optimistic delete: When an item is deleted, it is immediately removed from the local array before calling the onItemDelete callback. The parent is responsible for the actual SDK deletion call.
  • Page adjustment: After removing an item, if the current page exceeds the new total page count, the component automatically navigates to the last available page.
  • Loading skeleton: While fetching, three placeholder rows with animated pulse styling are shown.
  • Empty state: When the list contains no items, a centered message with a heart icon and customizable text is displayed.
  • Hydration safety: Uses an isMounted guard to prevent rendering client-only content during server-side rendering.
  • Item type detection: Products are identified by the presence of productId, clusters by clusterId. This determines whether add-to-cart or a "View cluster" link is shown.

SDK Services

The component uses FavoriteListService from propeller-sdk-v2 to fetch the favorite list and its items.

How it works

  1. A FavoriteListService instance is created from the provided graphqlClient.
  2. service.getFavoriteList(variables) is called with variables built internally from props.
  3. The response contains the list metadata plus products and clusters as ProductsResponse objects. Both are merged into a single array for display.

Variables built internally

The component constructs query variables automatically based on props:

{
id: props.favoriteListId,
language: props.language || 'NL',
priceCalculateProductInput: {
taxZone: 'NL',
customerId: /* from user if Customer */,
contactId: /* from user if Contact */,
companyId: /* from user.company if Contact */,
},
imageSearchFilters: { page: 1, offset: 1 },
imageVariantFilters: {
transformations: [{
name: 'cart_thumb',
transformation: { format: 'WEBP', height: 200, width: 200, fit: 'BOUNDS' },
}],
},
}

Price calculation input adapts to the user type: customerId for B2C Customer users, contactId + companyId for B2B Contact users.

Note: taxZone is hardcoded to 'NL' — there is no prop to override it (unlike ProductGrid which accepts a taxZone prop). Image filters are also hardcoded to page: 1, offset: 1 search filters and cart_thumb 200x200 WEBP variant.

GraphQL Queries and Mutations

The SDK's FavoriteListService.getFavoriteList() sends a query similar to:

query FavoriteList(
$id: String!
$language: String
$priceCalculateProductInput: PriceCalculateProductInput
$imageSearchFilters: ImageSearchInput
$imageVariantFilters: ImageVariantSearchInput
) {
favoriteList(id: $id) {
id
name
description
products(
language: $language
calculatePrice: $priceCalculateProductInput
) {
items {
productId
sku
name {
value
}
price {
net
gross
}
media(input: $imageSearchFilters) {
images(input: $imageVariantFilters) {
url
}
}
inventory {
totalQuantity
}
}
}
clusters(
language: $language
calculatePrice: $priceCalculateProductInput
) {
items {
clusterId
sku
name {
value
}
price {
net
gross
}
media(input: $imageSearchFilters) {
images(input: $imageVariantFilters) {
url
}
}
}
}
}
}