useProduct
Keeps a live copy of a product that follows price and stock changes from Salla option pickers, and can reload its details.
import { useProduct, UseProductOptions, UseProductResult } from '@salla.sa/twilight-theme-engine/hooks/useProduct';In plain words
A product page starts with the product data the server loaded. When a shopper picks a size or color, the price or stock can change. useProduct(product) gives you a product that updates itself when Salla's option components report a new price, or report that the chosen combination is unavailable.
reload() asks the Salla SDK for the product again and replaces the copy.
Signature
function useProduct(initialProduct: Product, options?: UseProductOptions): UseProductResult
interface UseProductOptions {
isPreview?: boolean; // true while initialProduct is a list-card stand-in
}
interface UseProductResult {
product: Product;
reload: () => Promise<void>;
}Try it live
Loading a product…
import { useProduct } from '@salla.sa/twilight-theme-engine/hooks/useProduct';
import { useMoney } from '@salla.sa/twilight-theme-engine/hooks/useMoney';
import type { Product } from '@salla.sa/twilight-theme-engine/types';
export function LivePrice({ initial }: { initial: Product }) {
const { product } = useProduct(initial); // follows the SDK's option and price events
const { format } = useMoney();
return (
<p>
{format(product.price, { currency: product.currency })}
{product.is_out_of_stock && <span> · out of stock</span>}
</p>
);
}
Example
import { useProduct } from '@salla.sa/twilight-theme-engine/hooks/useProduct';
import { useMoney } from '@salla.sa/twilight-theme-engine/hooks/useMoney';
import type { Product } from '@salla.sa/twilight-theme-engine/types';
export function LivePrice({ initial }: { initial: Product }) {
const { product } = useProduct(initial);
const { format } = useMoney();
return (
<div className="live-price">
{format(product.price, { currency: product.currency })}
{product.is_out_of_stock && <p>This combination is out of stock.</p>}
</div>
);
}
How it behaves
On the server, and until effects run,
productis simplyinitialProduct. All subscriptions are made in effects, in the browser.Salla.product.event.onPriceUpdatedmergesprice,sale_price,regular_price,is_on_sale(fromhas_sale_price) andweight, and setsis_out_of_stocktofalse.product::price.updated.failedsetsis_out_of_stocktotrue.product:options.updatedcallsreload().reload()callsSalla.api.product.getDetails(String(product.id), ['images', 'category'])and, on success, replaces the whole state withresponse.data. Network errors are ignored.The state is re-seeded from
initialProductonly wheninitialProduct.idoroptions.isPreviewchanges, never because a new object with the same id arrived. That keeps live option and price changes from being thrown away by a refetch.Pass
isPreview: truewhileinitialProductis a list-card stand-in (for example the placeholder data ofproduct.queries.detail) so the hook re-seeds when the full details for the same id arrive.The engine's
ProductPageuses it for the whole product page.
Gotchas
Props that change without a new
iddo not reachproduct. If you refetch the same product and want the new data, passisPreview(true, then false) or remount the component with akey.reload()asks only for images and category. The Store API then omitsoptions,skus,brand,tagsandrating(checked against the demo store), and the reloaded product replaces yours whole, so components reading those fields lose them.Every subscription needs
window.Sallawhen its effect first runs. If the SDK loads later, the price listeners are never attached.Price events come from Salla's product web components. Plain HTML inputs for options fire nothing, so the price will not follow them.