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

getActiveScope, getActiveScopeId

functionAdvancedserverbrowserlive demo

Return the store branch (scope) the shopper picked, or null; the API client sends it, and scope-sensitive query keys include it.

import { getActiveScope, getActiveScopeId } from '@salla.sa/twilight-theme-engine/api/client';

In plain words

Some stores sell from several branches or markets, and stock can differ between them. When a shopper picks one, that choice is the scope. getActiveScope() returns it as { id, type, allocation_type, allocation_id }, and getActiveScopeId() returns only the id. Both return null when nothing is picked.

The engine sends the scope with every API request and puts it in the cache keys of products and categories, so one branch never sees another branch's data.

Signature

function getActiveScope(): ActiveScope | null
function getActiveScopeId(): string | null    // getActiveScope()?.id ?? null

// Not exported; use ReturnType<typeof getActiveScope>
interface ActiveScope {
  id: string;
  type?: string;
  allocation_type?: string;
  allocation_id?: string;
}

Try it live

The branch (scope) the shopper picked, and how it ends up inside the query keys of scope-sensitive lists.Try this: the demo store sells from one place, so the scope is null. On a store with branches, pick one and press Read again.
Storefront canvas · en · LTR
Runs in the browser…
What a theme writes
import { useQuery } from '@tanstack/react-query';
import { api, getActiveScope } from '@salla.sa/twilight-theme-engine/api/client';

/** A custom call whose answer depends on the branch: put the scope in the key. */
export function BranchOffers() {
  const { data } = useQuery({
    queryKey: ['custom-offers', { scope: getActiveScope() }],
    queryFn: () => api.get('products?source=offers').json<{ data: unknown[] }>(),
  });
  return <span>{data?.data.length ?? 0} offers here</span>;
}

Example

app/hooks/useBranchStock.ts
import { useQuery } from '@tanstack/react-query';
import { api, getActiveScope } from '@salla.sa/twilight-theme-engine/api/client';

/** A custom request whose answer depends on the branch: the scope belongs in the key. */
export function useBranchStock(productId: string) {
  return useQuery({
    queryKey: ['branch-stock', productId, { scope: getActiveScope() }],
    queryFn: () =>
      api.get('products/' + productId + '/details').json<{ data: { quantity?: number } }>(),
  });
}

How it behaves

  • Order: Salla.storage.get('scope') from the Salla SDK; while that storage is not ready, the scope cookie; then the scope twilightMiddleware read from the same cookie on the server; then null.

  • A null from the SDK storage wins over an old cookie: it means the shopper cleared the choice. Only storage that is not ready yet falls through to the cookie.

  • The root loader mirrors the SDK choice into the scope cookie (syncScopeCookie), so the server renders the branch the browser picked.

  • The client sends s-scope-id, plus s-scope-type, s-scope-allocation-type and s-scope-allocation-id when present. store.settings also adds scope=<id> to its URL.

  • Keys that include it: product.queries.list, product.queries.detail, the product preview key, category.queries.list, wishlist.queries.list and store.queries.settings.

Gotchas

  • It is read when you call it. A key built before the shopper switched branch still names the old one: build query options during render.

  • A custom query for scope-sensitive data (products, stock, categories) without the scope in its key serves one branch's cached answer to another.

  • getActiveScopeId() alone names the market, not the branch allocation, and the API answers stock per allocation. Put the whole getActiveScope() object in keys.

Related

Source and docs