---
description: TanStack Query v5 conventions
globs: ["src/**/*.{ts,tsx}"]
alwaysApply: false
---

# TanStack Query v5

- Define queries once with `queryOptions()` in `src/queries/<entity>.ts` and reuse them in `useQuery`, `useSuspenseQuery`, `prefetchQuery` and `ensureQueryData`.
- Query keys are arrays that start with the entity and include every variable the fetcher uses: `['todos', 'list', { status, page }]`. Keep a key factory per entity.
- The `queryFn` throws on non-2xx responses (check `res.ok`); never return error objects as data.
- Use v5 names: `isPending` (not `isLoading` for "no data yet"), `gcTime` (not `cacheTime`), `placeholderData: keepPreviousData` for pagination. Single object signature only.
- Mutations: `useMutation` then `queryClient.invalidateQueries({ queryKey: ['todos'] })` in `onSettled`. Use optimistic updates only when the UX needs them, with rollback in `onError`.
- Set a sensible `staleTime` per query instead of disabling refetching globally.
- Server state stays in Query. Don't copy query data into `useState` or a global store.
- Dependent queries use `enabled: !!id`.

## Don't
- Don't call `fetch` in `useEffect` for server data.
- Don't create a `QueryClient` inside a component render.

## Done when
Typecheck and tests pass; tests wrap components in a fresh `QueryClient` with `retry: false`.
