name: react-query-best-practices description: Audit React Query usage for best practices — key factories, staleTime, mutations, and server state ownership
React Query Best Practices
Arguments:
- scope: what to analyze (default: your current changes). Examples: "diff to main", "PR #123", "src/hooks/queries/", "whole codebase"
- fix: whether to apply fixes (default: true). Set to false to only propose changes.
User arguments: $ARGUMENTS
Context
This codebase uses React Query (TanStack Query) as the single source of truth for all server state. All query hooks live in hooks/queries/. Zustand is used only for client-only UI state. Server data must never be duplicated into useState or Zustand outside of mutation callbacks that coordinate cross-store state.
References
Read these before analyzing:
- https://tkdodo.eu/blog/practical-react-query — foundational defaults, custom hooks, avoiding local state copies
- https://tkdodo.eu/blog/effective-react-query-keys — key factory pattern, hierarchical keys, fuzzy invalidation
- https://tkdodo.eu/blog/react-query-as-a-state-manager — React Query IS your server state manager
Rules to enforce
Query key factories
- Every file in
hooks/queries/must have a hierarchical key factory with anallroot key - Keys must include intermediate plural keys (
lists,details) for prefix invalidation - Key factories are colocated with their query hooks, not in a global keys file
Query hooks
- Every
queryFnmust forwardsignalfor request cancellation - Every query must have an explicit
staleTime(default 0 is almost never correct) keepPreviousData/placeholderDataonly on variable-key queries (where params change), never on static keys- Use
enabledto prevent queries from running without required params
Mutations
- Use
onSettled(notonSuccess) for cache reconciliation — it fires on both success and error - For optimistic updates: save previous data in
onMutate, roll back inonError - Use targeted invalidation (
entityKeys.lists()) not broad (entityKeys.all) when possible - Don't include mutation objects in
useCallbackdeps —.mutate()is stable
Server state ownership
- Never copy query data into useState. Use query data directly in components.
- Never copy query data into Zustand stores (exception: mutation callbacks that coordinate cross-store state like temp ID replacement)
- The query cache is not a local state manager —
setQueryDatais for optimistic updates only - Forms are the one deliberate exception: copy server data into local form state with
staleTime: Infinity
Steps
- Read the references above to understand the guidelines
- Analyze the specified scope against the rules listed above
- If fix=true, apply the fixes. If fix=false, propose the fixes without applying.