Skip to content
Twilight React Playground
ثيم رائدaren

useWishlist

hookBeginnerserverbrowserlive demo

The shopper's wishlist, shared by every component on the page: check a product, and add, remove or toggle it through the Salla SDK.

import { useWishlist, resetWishlistStore, UseWishlistResult } from '@salla.sa/twilight-theme-engine/hooks/useWishlist';

In plain words

This powers the heart button on product cards. has(productId) tells you whether a product is saved, and toggle(productId) saves or unsaves it.

Every component that calls useWishlist() sees the same list, so when one heart changes, every other heart and counter on the page updates too. loading is true while a request is on its way, and error holds a message when one fails.

Signature

function useWishlist(): UseWishlistResult

type UseWishlistResult = UseToggleWithItems<number>;
// {
//   ids: number[];  count: number;
//   has: (id: number) => boolean;
//   add / remove / toggle: (id: number) => Promise<boolean>;
//   loading: boolean;  error: string | null;
// }

function resetWishlistStore(): void   // tests only

Try it live

Three real products and their heart buttons. The count line is a separate component reading the same wishlist.Try this: press a heart. As a guest, Salla asks you to log in and nothing is saved, yet toggle() resolves true and error stays empty.
Real requests to the demo store
Storefront canvas · en · LTR

Loading products…

What a theme writes
import { useWishlist } from '@salla.sa/twilight-theme-engine/hooks/useWishlist';

export function HeartButton({ productId }: { productId: number }) {
  const { has, toggle, loading } = useWishlist();
  const saved = has(productId);
  return (
    <button aria-pressed={saved} disabled={loading} onClick={() => void toggle(productId)}>
      {saved ? '♥' : '♡'}
    </button>
  );
}

Example

app/components/product/WishlistButton.tsx
import { useWishlist } from '@salla.sa/twilight-theme-engine/hooks/useWishlist';

export function WishlistButton({ productId }: { productId: number }) {
  const { has, toggle, loading } = useWishlist();
  const saved = has(productId);

  return (
    <button
      type="button"
      className="wishlist-button"
      aria-pressed={saved}
      disabled={loading}
      onClick={() => void toggle(productId)}
    >
      {saved ? 'Saved' : 'Save'}
    </button>
  );
}

How it behaves

  • The ids live in one module-level store read with useSyncExternalStore. The SDK listeners (Salla.wishlist.event.onAdded and onRemoved) are attached once for the whole page, on the first call in the browser.

  • On that first call the ids are read from Salla.storage.get('salla::wishlist') when it is non-empty, otherwise from localStorage under the same key. Every change is written back to localStorage.

  • The server snapshot is always [], so the server renders every product as not saved and the hearts fill in after hydration.

  • ids and count are shared; loading and error belong to each call of the hook.

  • add and remove call Salla.wishlist.add / remove and then update the shared ids themselves, so the page updates even without the SDK event.

  • resetWishlistStore() empties the shared ids, the React listeners and the "already subscribed" flag. It exists for unit tests (beforeEach(resetWishlistStore)); it does not detach the SDK listeners, so never call it in a running theme.

Gotchas

  • For a guest, Salla's SDK refuses with a plain message string and shows its own "log in" notice. The hook treats only an Error as failure and reads error.message, so add/toggle resolve true, error stays null and nothing is saved. Check window.Salla?.config.isGuest?.() yourself if you need to react to it.

  • Ids are numbers. has('123') is false for product 123: convert ids from URLs or data attributes with Number() first.

  • It subscribes only once: if window.Salla is not there on the very first call, the SDK listeners are never attached, and changes made by Salla web components will not reach the hook.

  • The ids are read once. They do not follow the SDK's later wishlist::storage.synced event, so ids the SDK loads for a logged-in customer after that first call show up only after a reload.

  • Without the SDK (a unit test, or a page where it failed to load), add and remove still change the local ids and localStorage and resolve true.

Related

Source and docs