agentsclimarketplace

React query

Skill Deadlymind/nanolama/skills/react-query

Manages server state on the Next.js 16 frontend with TanStack Query v5 — structured array query keys, staleTime/gcTime tuning, useQuery/useMutation with onSuccess invalidateQueries by key prefix, and the server-state vs client-state boundary. Use when fetching or caching API data, wiring a query-key factory, invalidating after a POST/PATCH/DELETE, fixing stale UI after a mutation, or deciding what belongs in Query vs Zustand. Not for the tenant-switch procedure itself and what to purge on switch (see tenant-session-switch), persisted UI/client state (see zustand-state), or typing the API payload shapes (see drf-zod-contract).From its SKILL.md

Install
npx -y skills add Deadlymind/nanolama --skill react-query

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

2 things to look at

  • 28 days oldThe repository was created 28 days ago. New is not bad, but a brand new repository carrying a familiar-sounding name is the shape a typosquat arrives in, and there has been no time for anyone else to find a problem with it.
  • 1 stars1 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

SKILL.md

5.8 KB, ~1.3k tokens by cl100k_base, as published. Nobody here has run it

React Query (server state with TanStack Query v5)

When to use

Any component that reads or writes data owned by the Django/DRF backend. Server state is asynchronous, shared, and can go stale under you — that is Query's job. Do not copy fetched rows into Zustand or useState; mirror-storing server data is the top source of stale-UI bugs on this stack.

Pattern

Three rules:

  1. Query keys are structured arrays from a factory, never ad-hoc strings. A key is ['tenant', tenantId, 'invoices', 'list', filters] — a stable, serializable description of what the data is, which under a per-tenant API includes whose it is. Prefix-matching then powers targeted invalidation.
  2. staleTime sets how long data is trusted; gcTime how long an unused cache lingers. Pick per resource — not the defaults everywhere.
  3. After a mutation, invalidate by key prefix in onSuccess. Don't manually patch the cache for the common case; let the refetch reconcile with the server.

Centralize keys in a factory, read with useQuery, write with useMutation, then invalidate by prefix in onSuccess:

// features/invoices/keys.ts — tenant is part of the key, not just a header
export const invoiceKeys = {
  all: (t: string) => ['tenant', t, 'invoices'] as const,
  lists: (t: string) => [...invoiceKeys.all(t), 'list'] as const,
  list: (t: string, filters: InvoiceFilters) => [...invoiceKeys.lists(t), filters] as const,
  detail: (t: string, id: number) => [...invoiceKeys.all(t), 'detail', id] as const,
};

// Read (v5 single-object signature; no-data status is `isPending`)
const tenantId = useUI((s) => s.activeEntrepriseId);   // see `zustand-state`
const { data, isPending, isError } = useQuery({
  queryKey: invoiceKeys.list(tenantId, filters),
  queryFn: () => fetchInvoices(filters),   // returns a Zod-parsed payload
  enabled: !!tenantId,
  staleTime: 30_000,   // 30s: trust the list before refetching
  gcTime: 5 * 60_000,  // keep unused cache 5min for fast back-nav
});

// Write, then prefix-invalidate so every matching list/detail refetches
const qc = useQueryClient();
const { mutate } = useMutation({
  mutationFn: (body: InvoiceInput) => createInvoice(body),
  onSuccess: () => qc.invalidateQueries({ queryKey: invoiceKeys.all(tenantId) }),
});
// mutate(values) — call from the form's submit handler

The server-state vs client-state boundary

Belongs in React QueryBelongs in Zustand / local state
API rows, lists, detailsSidebar open, active tab, theme
Anything the server ownsDraft form input before submit
Derived-from-fetched valuesSelected row id / UI filters*

*Filters live in client state, then feed into the query key — the key is the one place client and server state meet. Never duplicate the fetched rows.

Switching tenants

A tenant switch changes which rows every key means, so it runs as an ordered sequence — set the store, cancelQueries, remove the old tenant's entries, resubscribe sockets, let observers refetch. The order is the safety property; see tenant-session-switch for the full procedure and tenant-aware persistence.

Adapt to your repo

Rename invoices/invoiceKeys and the accessor (/api/invoices/) per resource, and keep one keys file per feature. Tune staleTime to how fast the data changes (near-real-time dashboards → low or 0; reference lists → minutes). Wrap the app once in a QueryClientProvider in a client boundary under the App Router. Make queryFn return the Zod-validated shape (see drf-zod-contract).

Gotchas

  • v5 renamed isLoadingisPending for the no-data state, cacheTimegcTime, and takes a single object — positional useQuery(key, fn) no longer exists.
  • invalidateQueries prefix-matches, so { queryKey: ['invoices'] } catches every list and detail; scoping too narrowly leaves stale sibling views.
  • staleTime: 0 (the default) refetches on every mount/focus — deliberate, but chatty for stable data. Set it up rather than leaving it implicit.
  • Reading data off a mirrored Zustand copy defeats invalidation — the refetch updates the cache, not your snapshot. Read straight from useQuery.
  • Cookie-JWT auth means the browser sends the HttpOnly cookie automatically, but credentials: 'include' alone only covers reads — mutating mutationFn calls also need the X-CSRFToken header. Go through the shared client (see nextjs-module) rather than hand-rolling fetch in a queryFn.
  • Security-critical: the tenant belongs IN the query key, not only in the request header. Two tenants otherwise share the cache entry ['invoices','list',filters], and after a tenant switch the cache happily serves the previous tenant's rows — a cross-tenant leak your backend never sees. Scope every key under ['tenant', tenantId, ...] so two tenants can never collide in one entry (the purge on switch is tenant-session-switch's job). Client-side isolation is defence-in-depth, not the boundary — the server still enforces it (see multi-tenancy, rbac-permissions).

See also

  • nextjs-module
  • tenant-session-switch
  • zustand-state
  • drf-zod-contract

What ships with it

Read from the repository

Just SKILL.md. No reference files, no scripts.

Keep looking

Skills are one crate of 326,569. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.