Changing data in the browser: the Salla SDK
Add to cart and other actions go through window.Salla in the browser; listen to its events and clean up after yourself.
Loaders and queries read data. Changing it (adding to the cart, saving to the wishlist, applying a coupon, logging in) happens in the shopper's browser, through the Salla SDK: a script Salla loads on every storefront page and exposes as window.Salla.
The SDK does two jobs. Its methods send the change to Salla (Salla.cart.addItem(id)), and its events announce what happened (cart::item.added, cart::updated) to anything that listens: your components, Salla's own web components, installed apps.
// Plain JavaScript, in the browser, on any Salla storefront.
await Salla.onReady();
Salla.cart.event.onItemAdded((response, productId) => {
console.log('added', productId, 'cart now has', response.data.cart.count, 'items');
});
await Salla.cart.addItem(1303461379);
Add to your guest cart, watch the events
Pick a product and add it. The request goes to the demo store for your own visitor session, and the log shows each event as the SDK fires it. Then untick Listening and add again: the cart still changes, but nothing is logged, because the component removed its listeners.
A product marked needs options (a size, a color) cannot be added with its id alone: try one and read the log. Product pages use the engine's AddToCartForm, which sends the options the shopper chose.
Listening from a component
A React component listens in useEffect, because the SDK exists only in the browser, and returns a cleanup function that removes the listener. Without it, every time the component appears it adds one more listener, and old ones keep running after it is gone.
import { useEffect, useState } from 'react';
import { getSallaSDK } from '@salla.sa/twilight-theme-engine/utils';
export function CartCount() {
const [count, setCount] = useState<number | null>(null);
useEffect(() => {
const events = getSallaSDK()?.cart.event; // browser only: there is no SDK on the server
if (!events) return;
const onUpdated = (...args: unknown[]) => {
const summary = args[0] as { count?: number };
setCount(summary.count ?? null);
};
events.onUpdated?.(onUpdated);
return () => events.offUpdated?.(onUpdated); // the same function, or it is never removed
}, []);
return count === null ? null : <span className="cart-count">{count}</span>;
}
Calling a method needs no effect: an event handler only runs after a click, and a click only happens in the browser.
import { useState } from 'react';
import { getSallaSDK } from '@salla.sa/twilight-theme-engine/utils';
export function QuickAddButton({ productId }: { productId: number }) {
const [busy, setBusy] = useState(false);
return (
<button
type="button"
disabled={busy}
onClick={async () => {
setBusy(true); // a click only happens in the browser, so the SDK is there
try {
await getSallaSDK()?.cart.addItem(productId);
} catch {
// cart::item.added.failed has fired too: show a message if you need one
} finally {
setBusy(false);
}
}}
>
Add to cart
</button>
);
}
In engine terms
- The SDK sits on
windowunder two names,Sallaandsalla, and they are the same object. The engine's published types declare only the lower-case one (salla?: SallaSDK, src/types/salla-sdk.ts), so TypeScript followswindow.sallaand rejectswindow.Salla; reach it throughgetSallaSDK()from@salla.sa/twilight-theme-engine/utils, which returns it, orundefinedon the server, and is typed either way.TwilightProvidercallsSalla.init()and awaitsSalla.onReady()before it dispatchestheme::readyondocument. - In the SDK each cart event helper is generated:
onItemAdded(fn)storesfn.asyncWrapperand listens with it;offItemAdded(fn)removesfn.asyncWrapper ?? fn. Event names arecart::<name>:updated,item.added,item.added.failed,item.updated,item.deleted,coupon.added…. itemAddedfirst dispatchesupdatedwith the new cart summary ({ id, count, total, sub_total, … }), thenitem.addedwith(response, productId). The summary is also stored underSalla.storage.get('cart.summary').Salla.cart.addItem(id)with a bare id uses the quick-add endpoint; an object with aquantity, such as{ id, quantity: 2 }, uses the full add-item endpoint.- Nothing in the engine re-reads a cart query after the SDK changes the cart: refetch on
onUpdatedyourself, as the CartSummary demo does. - Engine hooks built on the SDK: useWishlist, useCoupon, useBlogLike; components: AddToCartForm. Reference for the accessor: getSallaSDK.