Recipes
Patterns for a site already set up as in Getting started.
The proxy
Create it once and use q everywhere. Its types come from augur-api, so autocomplete shows every SDK method.
// lib/augur-query.ts
"use client";
import { createServerQueryProxy } from "@simpleapps-com/augur-server/hooks";
export const q = createServerQueryProxy();import { useMutation, useQuery } from "@tanstack/react-query";
import { q } from "@/lib/augur-query";
const { data: item } = useQuery(q.items.invMast.doc.get(invMastUid));
const { data: price } = useQuery(q.pricing.priceEngine.list({ itemId: "SKU-1", customerId }));
const addLine = useMutation(q.commerce.cartLine.add.create());Per-call options go after the spread: useQuery({ ...q.items.invMast.doc.get(invMastUid), enabled: invMastUid > 0, select }). Debounce the input (useDebounce) before passing it in.
SSR prefetch
Prefetch with createAugurOptions from @simpleapps-com/augur-hooks/server and wrap the page in HydrationBoundary; Getting started has the full page. The server and the browser MUST build the query through a proxy so the keys match.
Cart changes
useCartActions updates the cart store at once and rolls back if your server action fails:
import { useCartActions } from "@simpleapps-com/augur-hooks";
import { addToCart, deleteItemsFromCart, updateCartLines } from "@/lib/actions/commerce";
const cart = useCartActions({ addToCart, updateCartLines, deleteItemsFromCart, toast: { info: toast.info, error: toast.error } });
await cart.addToCart({ invMastUid: 123, quantity: 1, unitOfMeasure: "EA" });
await cart.updateQuantity(123, 3);
await cart.removeFromCart(123);It also returns clearCart, isInCart, getItemQuantity, cartLines, cartHdrUid and invalidateCartCache. For other writes, use the proxy's mutation options.
Category items with filters
serializeCategoryFilters and paginateOffset (augur-utils) turn TItemsFilters into the SDK's params: JSON-encoded filters, stock included, and sortByPrefix applied when both it and sortBy are set.
import { useInfiniteQuery } from "@tanstack/react-query";
import { paginateOffset, serializeCategoryFilters } from "@simpleapps-com/augur-utils";
import { queryAction } from "@simpleapps-com/augur-server/query-action";
import { siteConfig } from "@/lib/augur-config";
import { q } from "@/lib/augur-query";
const page = (pageParam: number) =>
serializeCategoryFilters(paginateOffset(filters, pageParam), { sortByPrefix: siteConfig.items?.sortByPrefix });
const { data, fetchNextPage } = useInfiniteQuery({
queryKey: q.items.categories.items.list(itemCategoryUid, page(0)).queryKey,
queryFn: ({ pageParam }) =>
queryAction("items.categories.items.list", itemCategoryUid, page(pageParam)) as Promise<{ total: number }>,
initialPageParam: 0,
getNextPageParam: (last, _all, lastPage) =>
(lastPage + 1) * filters.limit < last.total ? lastPage + 1 : undefined,
});An infinite query fetches a different page per pageParam, so it takes the proxy's key and its own queryFn calling queryAction, the server action behind the proxy (which resolves to the response's data).
Search with filters
serializeSearchParams maps the same filters to the search endpoint's names: limit to size, offset to from, searchType: "query", a categoryUid to classId5List, and sourceFieldsList (default display_desc):
const params = serializeSearchParams(paginateOffset(filters, pageParam), {
categoryUid,
sourceFields: siteConfig.search?.sourceFields,
});
useQuery(q.openSearch.itemSearch.list(params));Errors
- Reads: let React Query handle them (retries,
isError,error). Don't wrap reads intry/catchorsafeAction. - Writes in server actions: wrap them in
safeAction, which gives{ ok: true, data }or{ ok: false, error }instead of throwing.
"use server";
import { getSiteActions, safeAction } from "@simpleapps-com/augur-server";
type CartItem = { invMastUid: number; quantity: number; unitOfMeasure: string };
export async function addToCart(cartHdrUid: number, items: CartItem[]) {
return safeAction(() => getSiteActions().commerce.addToCart(cartHdrUid, items));
}Which hooks need what
| Hook | Needs |
|---|---|
useAugurOptions |
AugurHooksProvider (or AugurSiteProvider) |
useCartPricing |
The provider, and a customer from auth or site.defaults.customerId |
useCartActions |
A QueryClientProvider; its server actions come in as callbacks |
useFormatPrice |
The provider; reads site.formatting when given |
useStock |
The provider |
Upgrading
Each augur package releases on its own, so upgrade any of them on its own:
- Set the new version in
package.jsonby hand. - Run
pnpm install. Don't usepnpm updateorpnpm add -L: they re-resolve shared dependencies such as React and can leave two copies. - Check
pnpm ls reactshows one copy. - Run
pnpm augur-doctor .and your quality gate.
Shared helpers
A helper every site needs goes into a package. A transform only some sites need lands in augur-utils once at least two sites want it; until then it stays in the site.