Product preview cache
The list-card cache that lets a product's details paint at once from the card you already have, before the full details arrive.
import { rememberProductPreview, getProductPreview, productPreviewQueryKey, productDetailQueryKey } from '@salla.sa/twilight-theme-engine/api/product';In plain words
A product list already knows each product's name, price and image. When a shopper opens one, the engine shows that card data right away and fills in the rest (photos, options) when the details arrive.
These four functions run that cache. product.list and wishlist.list feed it for you. Call rememberProductPreview(product) when your theme gets products some other way.
Signature
function rememberProductPreview(product: Product): void
function getProductPreview(id: string): Product | undefined
function productPreviewQueryKey(id: string):
readonly ['products', 'preview', string, { scope: ActiveScope | null }]
function productDetailQueryKey(id: string):
readonly ['products', 'detail', string, { scope: ActiveScope | null }]Try it live
import { useQuery } from '@tanstack/react-query';
import { api } from '@salla.sa/twilight-theme-engine/api/client';
import { rememberProductPreview } from '@salla.sa/twilight-theme-engine/api/product';
import type { Product } from '@salla.sa/twilight-theme-engine/types';
/** Products fetched without product.list: remember them so a quick view paints at once. */
export function useOfferCards() {
return useQuery({
queryKey: ['offer-cards'],
queryFn: async () => {
const { data } = await api.get('products?source=offers&per_page=8').json<{ data: Product[] }>();
data.forEach(rememberProductPreview);
return data;
},
});
}
Example
import { useQuery } from '@tanstack/react-query';
import { api } from '@salla.sa/twilight-theme-engine/api/client';
import { rememberProductPreview } from '@salla.sa/twilight-theme-engine/api/product';
import type { Product } from '@salla.sa/twilight-theme-engine/types';
/** Products fetched without product.list: remember them so a quick view paints at once. */
export function useOfferCards() {
return useQuery({
queryKey: ['offer-cards'],
queryFn: async () => {
const { data } = await api
.get('products?source=offers&per_page=8')
.json<{ data: Product[] }>();
data.forEach((item) => rememberProductPreview(item));
return data;
},
});
}
How it behaves
rememberProductPreviewwrites the card under the preview key of the engine QueryClient (from the twilight context). It does nothing when the details for that id are already cached successfully, or when there is no QueryClient.Previews live under their own key so a card never counts as a successful details fetch:
product.queries.detail(id)still requests the details and uses the card only asplaceholderData.getProductPreviewreads the preview key first. When it is empty (previews are left out of the server-rendered HTML), it scans the cached['products', 'list']and['wishlist', 'list']queries of the same branch scope, remembers what it finds, and returns the match.productPreviewQueryKeywraps the id inString();productDetailQueryKeyuses it as given. Pass strings to both.
Gotchas
A list query whose key does not end with a
{ scope }object never feeds previews:getProductPreviewskips it, as it skips lists cached under another branch. A custom list query should follow the engine's key shape, or callrememberProductPreviewitself.Both keys read the active scope when called; a key built before a branch change no longer matches the cache.
Related
Fetches one product with everything a product page needs: images, options, SKUs, brand, tags, rating and bundle contents.
product.listLists products from any source (latest, offers, a category, a brand, a search) with page size, sort, filters and cursor pagination.
shouldDehydrateQuery, isProductPreviewQueryThe engine's rule for which cached queries travel inside server-rendered HTML: successful and still-loading ones, never list-card previews.
Source and docs
- Engine source:
packages/theme-engine/src/api/product.ts