Product Catalog

Search and suggestions

Two calls cover search: products.list with q for a results page, and suggest for the dropdown under a search box.

Suggestions

suggest(q, { limit }) (GET /v1/storefront/suggest) answers what matches the start of what the shopper typed. q must be 2 to 100 characters; anything else answers 400 INVALID_REQUEST.

KeyHoldsHow many
productshandle, title, images, priceRange and availability, matched on title, brand and product typeUp to limit: 6 by default, at most 8
categorieshandle, name and productCountUp to 5
brandsname and productCountUp to 5
tagstag and productCountUp to 5
collectionshandle, title and productCountUp to 5

Categories, brands, tags and collections match by case-insensitive prefix and come most products first. Every name comes from public products only, so a draft title, a hidden attribute or an archived brand never shows up. Suggestions take the client's currency and locale. They always show list prices: a suggestion request never carries the customer token, so a signed-in shopper's group prices (see Customer prices) appear on product pages and cards, not here.

Suggestions
import { createCatalogClient } from '@usethrottle/catalog';

const catalog = createCatalogClient({
  publishableKey: 'pc_pk_your_browser_key',
});

// `q` must be 2 to 100 characters; a shorter one answers 400 INVALID_REQUEST.
const suggestions = await catalog.suggest('bre', { limit: 6 });

for (const product of suggestions.products) {
  console.log('product', product.handle, product.title, product.priceRange);
}
for (const category of suggestions.categories) {
  console.log('category', category.handle, category.name, category.productCount);
}
for (const brand of suggestions.brands) console.log('brand', brand.name);
for (const tag of suggestions.tags) console.log('tag', tag.tag);
for (const collection of suggestions.collections) {
  console.log('collection', collection.handle, collection.title);
}

One request per keystroke pause. Without keyInQuery the answer is cached under the guest partition. With keyInQuery: true (1.2.0 and later), suggest bypasses the SDK's ETag cache and sends no If-None-Match: the request carries only accept plus any client headers you set, so it needs no preflight (a client with custom headers preflights again). See Keys and channels.

Link each kind where it belongs: a product to its page, a category or collection to its page, a tag to products.list({ tags: [tag] }), and a brand to a shop page filtered by your brand filter (its key is whatever the merchant named it in Storefront › Filters).

Search as you type in React

useSuggest(q, { debounceMs, limit }) from @usethrottle/catalog/react sends no request while the trimmed text is shorter than 2 characters, and one request per pause in typing: debounceMs, 200 ms by default. Answers are cached in the provider, so typing back to an earlier text shows its suggestions at once. Like every hook, it answers { data, dataFor, error, isLoading, isPreviousData, isStale, isValidating, refetch }; with keepPreviousData: true the last suggestions stay while the next ones load, and dataFor.q is the text they answer.

A search box
'use client';
import { useState } from 'react';
import { useSuggest } from '@usethrottle/catalog/react';

// Inside a <CatalogProvider>. No request below 2 characters, and one request
// per pause in typing (200 ms by default).
export function SearchBox() {
  const [q, setQ] = useState('');
  const { data, isLoading } = useSuggest(q, { debounceMs: 250, limit: 5 });

  return (
    <form action="/search">
      <input
        name="q"
        value={q}
        onChange={(event) => setQ(event.target.value)}
        autoComplete="off"
      />
      {isLoading ? <p>Searching…</p> : null}
      <ul>
        {data?.products.map((product) => (
          <li key={product.handle}>
            <a href={`/products/${product.handle}`}>
              {product.title ?? product.handle}
            </a>
          </li>
        ))}
        {data?.categories.map((category) => (
          <li key={category.handle}>
            <a href={`/categories/${category.handle}`}>{category.name}</a>
          </li>
        ))}
      </ul>
    </form>
  );
}

Suggestions have their own rate limit for browser, app and legacy keys: 600 requests a minute per visitor, twice the limit of the other routes, so a fast typist is not cut off. The React page covers the provider and the cache.