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

category

objectBeginnerserverbrowserlive demo

Reads the store's category tree, or a single category, from the Salla API; the tree is cached per branch.

import { category } from '@salla.sa/twilight-theme-engine/api/category';

In plain words

Categories are how a store groups its products: Furniture, and Chairs inside it. category.list() returns the whole tree (each category lists its sub_categories), and category.find(id) returns one category.

To show the products of a category, ask product.list with source: 'categories' and the category's numeric id.

Signature

category.list(): Promise<Category[]>
category.find(id: string): Promise<Category>
category.findOrThrow(id: string): Promise<Category>   // any failure → NotFoundError
category.queries.list()        // key ['categories', 'list', { scope }]
category.queries.detail(id)    // key ['categories', 'detail', id]

interface Category {
  id: number | string;   // hashed, e.g. 'mgjZqj'
  id_?: number;          // numeric, e.g. 708738657
  name: string;
  url: string;
  description?: string;
  icon?: string | null;
  image?: string | null;
  products_count?: number;
  sub_categories?: Category[];
}

Try it live

category.queries.list() returns the store's category tree; the products of one category come from product.queries.list().Try this: compare id and id_ of a category: only the numeric id_ works as a products source.
Storefront canvas · ar · RTL

Loading categories…

Controls
What a theme writes
import { useQuery } from '@tanstack/react-query';
import { category } from '@salla.sa/twilight-theme-engine/api/category';
import { product } from '@salla.sa/twilight-theme-engine/api/product';

export function CategoryShelf() {
  const { data: categories = [] } = useQuery(category.queries.list());
  const first = categories[0];

  const { data: products } = useQuery({
    ...product.queries.list({ source: 'categories', sourceValue: first?.id_ ? [first.id_] : [], perPage: 4 }),
    enabled: first?.id_ != null, // the API answers 422 without a category
  });

  return (
    <section>
      <h2>{first?.name}</h2>
      <ul>
        {products?.items.map((item) => (
          <li key={item.id}>{item.name}</li>
        ))}
      </ul>
    </section>
  );
}

Example

app/components/CategoryPills.tsx
import { useQuery } from '@tanstack/react-query';
import { category } from '@salla.sa/twilight-theme-engine/api/category';
import { Link } from '@salla.sa/twilight-theme-engine/components/common';

export function CategoryPills() {
  const { data: categories = [] } = useQuery(category.queries.list());

  return (
    <nav className="category-pills">
      {categories.map((item) => (
        <Link key={item.id} to={item.url}>
          {item.name}
        </Link>
      ))}
    </nav>
  );
}

How it behaves

  • Endpoints: GET categories (returns data ?? []) and GET categories/{id}, both public. find accepts either id form: the demo store answered mgjZqj and 708738657 alike.

  • Only the list key carries the branch scope. The detail key does not, so one cached category is shared across branches.

  • The engine product-listing loader reads a category with queryClient.fetchQuery(category.queries.detail(id)) before listing its products.

  • url is an absolute address on the store domain (on a shared Salla host it includes the store username). The engine MainMenu hands such addresses to the engine Link unchanged.

Gotchas

  • Products want the numeric id_, in an array: product.list({ source: 'categories', sourceValue: [item.id_] }). The hashed id answers HTTP 422 (demo store).

  • id_ is optional in the type. Check it before building a products query, or the request goes out without a category and fails.

Related

Source and docs