> Markdown version of [Shopping Cart Example](https://vaadin.com/docs/latest/flow/ui-state/usage-examples/shopping-cart-example). Section index: [llms.txt](https://vaadin.com/docs/latest/flow/llms.txt)

# Shopping Cart Example

This example demonstrates building a reactive shopping cart with automatic total calculations. It shows how to manage dynamic lists of items and efficiently update the UI when items are added, removed, or modified.

## <a id="overview"></a>Overview

The shopping cart tracks:

- Cart items with quantities

- Discount codes

- Shipping options

- Real-time totals (subtotal, discount, tax, shipping, and grand total)

All calculations update automatically as users modify the cart, apply discounts, or change shipping options.

## <a id="state-management-with-signals"></a>State Management with Signals

The cart uses three signals to track its state:

`ShoppingCartSignals.java`

```java
// Create signals for cart state
ListSignal<CartItem> cartItemsSignal = new ListSignal<>();
ValueSignal<String> discountCodeSignal = new ValueSignal<>("");
ValueSignal<ShippingOption> shippingOptionSignal = new ValueSignal<>(
        ShippingOption.STANDARD);
```

- **`ListSignal<CartItem>`**

  Manages the list of cart items. Each item in the list is itself a signal (`ValueSignal<CartItem>`), enabling reactive updates when quantities change.

- **`ValueSignal<String>`**

  Tracks the discount code entered by the user.

- **`ValueSignal<ShippingOption>`**

  Tracks the selected shipping method.

## <a id="computed-totals"></a>Computed Totals

Each total is a computed signal that automatically recalculates when dependencies change.

### <a id="subtotal"></a>Subtotal

The subtotal sums the price of all items in the cart:

`ShoppingCartSignals.java`

```java
// Computed signal for subtotal
Signal<BigDecimal> subtotalSignal = Signal.computed(
        () -> cartItemsSignal.get().stream().map(ValueSignal::get)
                .map(item -> item.product().price()
                        .multiply(BigDecimal.valueOf(item.quantity())))
                .reduce(BigDecimal.ZERO, BigDecimal::add));
```

This computed signal reads from `cartItemsSignal`. It accesses each item’s signal value using `map(ValueSignal::get)`, then multiplies the product price by quantity. The result is summed using `reduce()`.

When cart items are added, removed, or quantities change, this signal recalculates automatically.

### <a id="discount"></a>Discount

The discount depends on both the discount code and the subtotal:

`ShoppingCartSignals.java`

```java
// Computed signal for discount
Signal<BigDecimal> discountSignal = Signal.computed(() -> {
    String code = discountCodeSignal.get();
    DiscountCode discount = validateDiscountCode(code);
    if (discount != null) {
        return subtotalSignal.get().multiply(discount.percentage())
                .divide(new BigDecimal("100"), 2, RoundingMode.HALF_UP);
    }
    return BigDecimal.ZERO;
});
```

This computed signal reads from both `discountCodeSignal` and `subtotalSignal`. When either changes, the discount recalculates.

### <a id="tax"></a>Tax

Tax is calculated on the discounted subtotal:

`ShoppingCartSignals.java`

```java
// Computed signal for tax (8%)
Signal<BigDecimal> taxSignal = Signal.computed(
        () -> subtotalSignal.get().subtract(discountSignal.get())
                .multiply(new BigDecimal("0.08"))
                .setScale(2, RoundingMode.HALF_UP));
```

This demonstrates chaining computed signals. The tax signal depends on `subtotalSignal` and `discountSignal`, both of which are themselves computed signals.

### <a id="shipping"></a>Shipping

Shipping cost depends on the selected shipping option:

`ShoppingCartSignals.java`

```java
// Computed signal for shipping cost
Signal<BigDecimal> shippingSignal = Signal
        .computed(() -> getShippingCost(shippingOptionSignal.get()));
```

### <a id="grand-total"></a>Grand Total

The final total combines all computed values:

`ShoppingCartSignals.java`

```java
// Computed signal for grand total
Signal<BigDecimal> totalSignal = Signal.computed(() -> subtotalSignal
        .get().subtract(discountSignal.get()).add(shippingSignal.get())
        .add(taxSignal.get()).setScale(2, RoundingMode.HALF_UP));
```

This computed signal depends on four other computed signals: `subtotalSignal`, `discountSignal`, `shippingSignal`, and `taxSignal`. When any of these changes, the total updates automatically.

## <a id="binding-totals-to-ui"></a>Binding Totals to UI

Each total is bound to a label that displays it:

`ShoppingCartSignals.java`

```java
// Totals display
Div totalsBox = new Div();

H3 summaryTitle = new H3("Order Summary");

Span subtotalLabel = new Span();
subtotalLabel.bindText(subtotalSignal.map(total -> "Subtotal: $"
        + total.setScale(2, RoundingMode.HALF_UP)));

Span discountLabel = new Span();
discountLabel.bindText(discountSignal.map(discount -> "Discount: -$"
        + discount.setScale(2, RoundingMode.HALF_UP)));
discountLabel.bindVisible(
        discountSignal.map(d -> d.compareTo(BigDecimal.ZERO) > 0));

Span shippingLabel = new Span();
shippingLabel.bindText(shippingSignal.map(shipping -> "Shipping: $"
        + shipping.setScale(2, RoundingMode.HALF_UP)));

Span taxLabel = new Span();
taxLabel.bindText(taxSignal.map(
        tax -> "Tax (8%): $" + tax.setScale(2, RoundingMode.HALF_UP)));

Span totalLabel = new Span();
totalLabel.bindText(totalSignal.map(
        total -> "Total: $" + total.setScale(2, RoundingMode.HALF_UP)));

totalsBox.add(summaryTitle, subtotalLabel, discountLabel, shippingLabel,
        taxLabel, totalLabel);
```

Key techniques:

- **`bindText()`**

  Updates the label text whenever the signal changes

- **`map()`**

  Transforms the signal value (e.g., formatting currency)

- **`bindVisible()`**

  Shows the discount label only when a discount is applied

## <a id="form-field-binding"></a>Form Field Binding

The discount code and shipping option use two-way binding:

`ShoppingCartSignals.java`

```java
TextField discountField = new TextField("Discount Code");
discountField.setPlaceholder("Enter SAVE10 or SAVE20");
discountField.bindValue(discountCodeSignal, discountCodeSignal::set);
```

`ShoppingCartSignals.java`

```java
ComboBox<ShippingOption> shippingSelect = new ComboBox<>(
        "Shipping Method", ShippingOption.values());
shippingSelect.setValue(ShippingOption.STANDARD);
shippingSelect.bindValue(shippingOptionSignal,
        shippingOptionSignal::set);
```

The `bindValue(signal, signal::set)` method creates two-way binding:

- When the user types in the field, the write callback (`signal::set`) updates the signal

- When the signal is updated programmatically, the framework reads via `signal.get()` and updates the field

This is different from read-only bindings like `bindText()`, which only update the UI when the signal changes.

## <a id="dynamic-list-rendering-with-children-binding"></a>Dynamic List Rendering with Children Binding

The core of the shopping cart is rendering the list of cart items. The `bindChildren()` method on any container component efficiently manages this:

`ShoppingCartSignals.java`

```java
cartItemsList.bindChildren(cartItemsSignal,
        itemSignal -> createCartItemRow(itemSignal, cartItemsSignal));
```

`bindChildren()` takes two parameters:

1. **`ListSignal`**: The signal containing the list of items

2. **Factory function**: A function that creates a component for each item signal

The factory function receives a `ValueSignal<CartItem>` for each item and creates the component once. The component then binds to this signal to receive updates. Crucially, component instances are created only when new items are added to the list. When item data changes (such as quantity updates), the existing component updates through its signal bindings rather than being recreated. This approach minimizes memory allocation and DOM operations: components are reused when items are reordered, detached only when items are removed, and their internal state updates reactively without component recreation.

## <a id="creating-cart-item-rows"></a>Creating Cart Item Rows

Each cart item is rendered as a row with reactive bindings:

`ShoppingCartSignals.java`

```java
private HorizontalLayout createCartItemRow(ValueSignal<CartItem> itemSignal,
        ListSignal<CartItem> cartItemsSignal) {
    HorizontalLayout row = new HorizontalLayout();

    Span nameLabel = new Span(itemSignal.map(item -> item.product().name()
            + " - $"
            + item.product().price().setScale(2, RoundingMode.HALF_UP)));

    IntegerField quantityField = new IntegerField();
    quantityField.setMin(1);
    quantityField.setMax(99);
    quantityField.setWidth("120px");
    quantityField.setStepButtonsVisible(true);

    // Two-way mapped signal for quantity
    quantityField.bindValue(itemSignal.map(CartItem::quantity),
            itemSignal.updater(CartItem::withQuantity));

    // Handle removal when quantity drops below 1
    quantityField.addValueChangeListener(e -> {
        Integer value = e.getValue();
        if (value == null || value < 1) {
            cartItemsSignal.remove(itemSignal);
        }
    });

    Span itemTotalLabel = new Span(itemSignal.map(item -> "$" + item
            .product().price().multiply(BigDecimal.valueOf(item.quantity()))
            .setScale(2, RoundingMode.HALF_UP)));

    Button removeButton = new Button("Remove", e -> {
        cartItemsSignal.remove(itemSignal);
    });

    row.add(nameLabel, quantityField, itemTotalLabel, removeButton);
    return row;
}
```

Key techniques used:

- **Two-way signal mapping**

  `itemSignal.map(CartItem::quantity)` reads the quantity, and `itemSignal.updater(CartItem::withQuantity)` creates a write callback that updates the quantity using the `withQuantity` wither method. Together with `bindValue()`, this enables two-way binding between the quantity field and the cart item.

- **Nested computed signal**

  `Signal.computed()` calculates the item total by multiplying price by quantity. This computed signal updates automatically when the quantity changes.

- **Removing items**

  Calling `cartItemsSignal.remove(itemSignal)` removes the item from the list. The component is automatically detached from the DOM.

See [Two-Way Signal Mapping](https://vaadin.com/docs/latest/flow/ui-state/local-signals.md#two-way-mapping) for more details on mapping signals to nested properties.

## <a id="adding-items-to-cart"></a>Adding Items to Cart

When a user adds a product, the cart checks if it’s already present:

`ShoppingCartSignals.java`

```java
private void addToCart(Product product,
        ListSignal<CartItem> cartItemsSignal) {
    cartItemsSignal.peek().stream().filter(
            signal -> signal.peek().product().id().equals(product.id()))
            .findFirst().ifPresentOrElse(
                    existing -> existing.set(existing.peek()
                            .withQuantity(existing.peek().quantity() + 1)),
                    () -> cartItemsSignal
                            .insertLast(new CartItem(product, 1)));
}
```

This logic:

1. Searches the cart for an item with the same product ID using `signal.get()`

2. If found, increments its quantity by updating the signal’s value using `signal.set()`

3. If not found, inserts a new cart item at the end of the list

When the quantity is updated on an existing item, the component row updates automatically without being recreated. Only the quantity field and item total update.

## <a id="reactivity-flow"></a>Reactivity Flow

Here’s what happens when a user changes a quantity:

1. User types in the quantity field

2. The two-way mapped signal updates the `CartItem` value

3. The item total computed signal recalculates

4. The subtotal computed signal recalculates

5. The discount computed signal recalculates (because it depends on subtotal)

6. The tax computed signal recalculates (because it depends on subtotal and discount)

7. The grand total computed signal recalculates (because it depends on all other totals)

8. All bound labels update automatically

All of this happens automatically without manual event listeners or state synchronization.

## <a id="when-to-use-listsignal"></a>When to Use ListSignal

`ListSignal` is ideal for:

- Single-user scenarios like this shopping cart

- Dynamic form fields (add/remove rows)

- Local selections and filters

- Any list where items are added, removed, or reordered

For multi-user scenarios where multiple users need to see the same list in real-time (like a collaborative task board), use `SharedListSignal` instead. See [Shared Signals](https://vaadin.com/docs/latest/flow/ui-state/shared-signals.md) for details.

## <a id="key-takeaways"></a>Key Takeaways

- **`ListSignal`**

  Manages dynamic lists with per-item reactivity. Each item is a `ValueSignal` that can be updated independently.

- **`bindChildren()`**

  Efficiently renders components from a list signal. Available on any container component (`HasComponents`), components are added, removed, and reordered without unnecessary recreation.

- **Two-way mapping**

  `signal.map(getter)` combined with `signal.updater(merger)` or `signal.modifier(setter)` enables direct two-way binding to form fields for nested properties.

- **Computed signal chains**

  Complex calculations can be broken into multiple computed signals that depend on each other. Changes propagate automatically through the chain.

- **Automatic updates**

  When any signal changes, all dependent computed signals and bindings update automatically without manual synchronization.
