# React to cart events (https://docs.salla.dev/twilight/react/recipes/react-to-cart-events)



Use this when your UI or analytics must follow the cart: a badge count, a mini-cart, a "you added..." message.

**Goal:** a component that shows the live cart count.

The Salla SDK (`Salla` in prose, `window.salla` in code) emits events such as `cart::updated`. You subscribe in a `useEffect` and unsubscribe on cleanup. The SDK can finish loading after the page hydrates, so read it inside the effect, never at module scope. The full event list and the rules are in [Salla SDK and events](https://docs.salla.dev/twilight/react/guide/salla-sdk-and-events).

### Write a subscription hook [#write-a-subscription-hook]

It subscribes by event name, and removes the same function on cleanup. If the SDK is not ready yet, it waits for the `theme::ready` document event. The reference theme's mobile bottom bar uses this pattern for its cart badge.

```tsx title="app/hooks/useCartUpdated.ts"
import { useEffect } from 'react';

type CartSummary = { count?: number | string | null };

export function useCartUpdated(onUpdated: (summary?: CartSummary) => void) {
  useEffect(() => {
    const listener = (...args: unknown[]) => onUpdated(args[0] as CartSummary | undefined);
    let bound = false;

    const bind = () => {
      const event = window.salla?.event;
      if (bound || !event?.on) return;
      event.on('cart::updated', listener);
      bound = true;
    };

    bind();
    document.addEventListener('theme::ready', bind);
    return () => {
      document.removeEventListener('theme::ready', bind);
      if (bound) window.salla?.event?.off?.('cart::updated', listener);
    };
  }, [onUpdated]);
}
```

### Use it in a component [#use-it-in-a-component]

Wrap the callback in `useCallback` so the effect does not resubscribe on every render.

```tsx title="app/components/CartWatcher.tsx"
import { useCallback, useState } from 'react';
import { useCartUpdated } from '../hooks/useCartUpdated';

export function CartWatcher() {
  const [count, setCount] = useState(0);
  const onUpdated = useCallback((summary?: { count?: number | string | null }) => {
    setCount(Number(summary?.count ?? 0));
  }, []);
  useCartUpdated(onUpdated);
  return <span>{count}</span>;
}
```

## Check it worked [#check-it-worked]

Add a product to the cart: `count` updates without a reload. Navigate away and back, then add again: it still updates once, not twice.

<Callout type="warn" title="Traps">
  * **Do not mix the two ways to subscribe.** Typed helpers exist (`salla.cart.event.onUpdated`, `onItemAdded`, `onItemUpdated`, `onItemDeleted`, each with a matching `off...`), but there is no `salla.cart.event.off(name, cb)`. A name-based listener is removed with `salla.event.off`.
  * **Subscribe by name in components that mount often.** The reference theme avoids `onUpdated` there because the helper wraps your callback and it leaked listeners in its case.
  * **`window.salla` is `undefined` during server rendering, and a stub without `event` until the SDK loads.** Guard the member (`window.salla?.event?.on`), subscribe inside `useEffect`, and use `theme::ready` as above.
  * **The initial count is not an event.** The reference theme also reads `salla.storage.get('cart.summary.count')` once on mount.
  * **The payload is loosely typed.** Treat it as optional and fall back to a default.
</Callout>

## Next [#next]

* [Track page views](https://docs.salla.dev/twilight/react/recipes/track-page-views): another browser-side subscription.
* [Salla SDK and events](https://docs.salla.dev/twilight/react/guide/salla-sdk-and-events): every event and the SSR rules.
