# Pathrule Pattern: TanStack Query (1.0.0)
# ::pathrule:package:tanstack-query

### [RULE] Never fetch in useEffect for server state  (path: /src)
<!-- scope: folder | priority: high | strict -->

Server state belongs to TanStack Query, not to component effects. Fetching in `useEffect` and storing the result in `useState` reintroduces every bug the library exists to solve: no caching, no dedupe, manual loading and error flags, and race conditions on fast navigation.

- Replace any `useEffect` that calls `fetch` or an API client with `useQuery` or `useSuspenseQuery`.
- Read status from `query.status`, `isPending`, and `isError` instead of hand-rolled `useState` flags.
- Use `enabled` (or component composition with `useSuspenseQuery`) for dependent queries rather than gating fetches inside an effect.
- Reserve `useEffect` for true side effects like subscriptions and DOM work, never for data loading.

---

### [RULE] Invalidate related queries after every mutation  (path: /src)
<!-- scope: folder | priority: high | advisory -->

A mutation that changes server data must tell the cache. Without invalidation the UI keeps rendering stale data until a refetch trigger happens to fire.

- In `onSuccess` (or `onSettled`), call `queryClient.invalidateQueries({ queryKey: keys.lists() })` for every list or detail the write touches.
- Prefer key-factory references over inline arrays so invalidation targets match the keys that were set.
- For optimistic UI, use `onMutate` to `cancelQueries`, snapshot with `getQueryData`, write with `setQueryData`, and roll back in `onError`; still invalidate in `onSettled`.
- Do not manually `setQueryData` as a substitute for invalidation unless you return the exact server shape.

---

### [MEMORY] Query key factories are the cache contract  (path: /src/api)

Query keys are how TanStack Query identifies, dedupes, and invalidates cache entries, so they live in one factory per entity rather than as scattered inline arrays.

- Shape each factory hierarchically: `all`, `lists()`, `list(filters)`, `details()`, `detail(id)` so a single `invalidateQueries({ queryKey: keys.all })` can clear a whole entity.
- Include every input a query depends on (filters, ids, pagination) in the key; changing the key is how a new fetch is triggered.
- Pair factories with `queryOptions()` from v5 so the same key, `queryFn`, and `staleTime` are reused across `useQuery`, `useSuspenseQuery`, `prefetchQuery`, and `setQueryData`.
- Keys must be serializable and order-stable; objects are compared by deep structure, not reference.

---

### [MEMORY] TanStack Query v5 defaults and staleTime tuning  (path: /src)

TanStack Query v5 (`@tanstack/react-query` 5.x) ships aggressive freshness defaults that surprise teams expecting cache-first behavior.

- `staleTime` defaults to `0`, so data is stale immediately and refetches on mount, window focus, and reconnect; set a realistic value like `5 * 60 * 1000` per query for data that does not change every second.
- `gcTime` (renamed from `cacheTime`) defaults to 5 minutes and controls when inactive cache entries are garbage collected, not staleness.
- Use `staleTime: Infinity` for data that only changes via your own mutations, and `staleTime: 'static'` for truly immutable data like feature flags.
- v5 removed the `suspense: true` flag on `useQuery`; use the dedicated `useSuspenseQuery`, and note it has no `enabled` option since loading is handled by Suspense and errors by an Error Boundary.

---

### [SKILL] tanstack-query-review  (path: /)

---
name: tanstack-query-review
description: Review checklist for TanStack Query v5 code. Use when adding or changing useQuery, useSuspenseQuery, useMutation, query keys, or QueryClient config in a React or Next.js codebase.
---

# TanStack Query review

- [ ] No server data is fetched in `useEffect` plus `useState`; every read uses `useQuery` or `useSuspenseQuery`
- [ ] Query keys come from a centralized factory, not inline ad-hoc arrays, and include every input the query depends on
- [ ] Shared queries are defined with `queryOptions()` and reused across hooks, prefetch, and `setQueryData`
- [ ] `staleTime` and `gcTime` are set deliberately rather than relying on the `0ms` stale default
- [ ] Every `useMutation` invalidates or updates the queries its write affects in `onSuccess` or `onSettled`
- [ ] Optimistic updates cancel in-flight queries, snapshot previous data, and roll back in `onError`
- [ ] Loading and error states read from `isPending`, `isError`, and Error Boundaries, not hand-rolled flags
- [ ] `enabled` or component composition handles dependent queries; nothing gates fetching inside an effect
