AgentStack
Browse Sign in
Browse Why AgentStack Sell Docs
Sign in
SKILL verified MIT Self-run

React Query

skill-deadlymind-nanolama-react-query · by Deadlymind

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 wha…

No reviews yet
0 installs
19 views
0.0% view→install

Install

$ agentstack add skill-deadlymind-nanolama-react-query

✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.

Security review

✓ Passed

No issues found. Passed automated security review. · v0.1.0 How review works →

  • Prompt-injection patterns
  • Secret / credential exfiltration
  • Dangerous shell & filesystem operations
  • Untrusted network calls
  • Known-malicious package signatures

What it can access

  • Network access No
  • Filesystem access No
  • Shell / process execution No
  • Environment & secrets No
  • Dynamic code execution No

From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.

View the full security report →

Verified badge

Passed review? Show it. Paste this badge into your README, it links to the public security report.

AgentStack Verified badge Links to your public security report.
[![AgentStack Verified](https://agentstack.voostack.com/badges/verified.svg)](https://agentstack.voostack.com/security/report/skill-deadlymind-nanolama-react-query)

Reliability & compatibility

Security review passed
0 installs to date
no reviews yet
24d ago

Declared compatibility

Claude CodeClaude Desktop

Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.

Preview Execution monitoring

We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.

How agent discovery & health will work →
Are you the author of React Query? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

About

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.

  1. **staleTime sets how long data is trusted; gcTime how long an unused

cache lingers.** Pick per resource — not the defaults everywhere.

  1. 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 Query | Belongs in Zustand / local state | | --- | --- | | API rows, lists, details | Sidebar open, active tab, theme | | Anything the server owns | Draft form input before submit | | Derived-from-fetched values | Selected 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

Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

Install and usage instructions live in the source repository linked above.

Reviews

No reviews yet, be the first.

Versions

  • v0.1.0 Imported from the upstream source.