useWishlist
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 onlyTry it live
Loading products…
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
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.onAddedandonRemoved) 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 fromlocalStorageunder the same key. Every change is written back tolocalStorage.The server snapshot is always
[], so the server renders every product as not saved and the hearts fill in after hydration.idsandcountare shared;loadinganderrorbelong to each call of the hook.addandremovecallSalla.wishlist.add/removeand 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
Erroras failure and readserror.message, soadd/toggleresolvetrue,errorstaysnulland nothing is saved. Checkwindow.Salla?.config.isGuest?.()yourself if you need to react to it.Ids are numbers.
has('123')isfalsefor product 123: convert ids from URLs or data attributes withNumber()first.It subscribes only once: if
window.Sallais 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.syncedevent, 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),
addandremovestill change the local ids andlocalStorageand resolvetrue.
Related
Source and docs
- Engine source:
packages/theme-engine/src/hooks/useWishlist.ts