React Query

Ultimate set of rules and best practices for using React Query effectively in production environments

# React Query Rules

This document outlines the ultimate set of rules and best practices for using React Query effectively in production environments.

---

## Core Principles

1. **Think in Server State, Not Client State**
   - Use React Query for remote data (from APIs).
   - Use component/local state, React Context, or global state management libraries (e.g., Redux, Zustand, Jotai) for client-specific state (e.g., modal open/close, form state).

2. **Always Prefer `useQuery` Over Manual Fetch**
   - Encapsulate API logic in `useQuery` instead of `useEffect` + `useState`.
   - Example:
     ```ts
     const { data, isLoading } = useQuery(['todos'], fetchTodos);
     ```

3. **Use Query Keys Strategically**
   - Keys must be unique and predictable.
   - Use arrays: `['todos', userId]` instead of string concatenation.
   - Extract query keys into a central file for consistency.

---

## Organizing Queries

4. **Normalize Query Keys**
   - Create a `queryKeys` helper to avoid duplication.
   - Example:
     ```ts
     export const queryKeys = {
       todos: (userId: string) => ['todos', userId],
       profile: ['profile'],
     };
     ```

5. **Encapsulate Queries in Custom Hooks**
   - Define reusable hooks like `useTodos(userId)` or `useUserProfile()`.
   - Keeps components clean and ensures consistent query usage across the app.

6. **Set `staleTime` and `cacheTime` Wisely**
   - `staleTime`: How long data is considered "fresh". Set based on data volatility.
   - `cacheTime`: How long unused data stays cached before garbage collection.

7. **Avoid Over-Fetching**
   - Use `enabled: false` for conditional queries.
   - Example: wait until `userId` is available.
     ```ts
     const { data } = useQuery(queryKeys.todos(userId), fetchTodos, {
       enabled: !!userId,
     });
     ```

---

## Mutations

8. **Always Use `useMutation` for POST/PUT/DELETE**
   - Example:
     ```ts
     const mutation = useMutation(updateTodo, {
       onSuccess: () => queryClient.invalidateQueries(queryKeys.todos()),
     });
     ```

9. **Leverage Optimistic Updates**
   - Use `onMutate`, `onError`, and `onSettled` for smooth UX.
   - Rollback changes on failure to maintain consistency.

---

## Data Synchronization

10. **Invalidate Queries Intentionally**
    - Use `invalidateQueries` after mutations that affect cached data.
    - Narrow scope: invalidate only related queries instead of all queries.

11. **Background Refetching**
    - Use `refetchOnWindowFocus` and `refetchInterval` sparingly.
    - Enable only when data must stay real-time fresh.

---

## Performance

12. **Batch Queries with `useQueries`**
    - Combine multiple queries where necessary.
    - Prevents unnecessary network requests.

13. **Paginated & Infinite Queries**
    - Use `useInfiniteQuery` for endless scrolling.
    - Always return `nextCursor` or pagination info from the API.

14. **Prefetch & Hydrate**
    - Use `queryClient.prefetchQuery` for routes you know the user will visit soon.
    - For SSR/SSG, use `dehydrate` and `Hydrate` to deliver cached data on load.

---

## Error & Loading States

15. **Centralize Error Handling**
    - Use React Query's global `onError` handler.
    - Provide user-friendly error boundaries in the UI.

16. **Never Block UI**
    - Avoid full-page global spinners.
    - Use skeletons, placeholders, or incremental loading instead.

---

## Testing

17. **Mock QueryClient in Tests**
    - Use `QueryClientProvider` with an isolated `QueryClient` in each test.
    - Avoid leaking cache across tests.

18. **Test Hook Logic, Not Library Behavior**
    - Don't test React Query itself—only your query functions and business logic.

---

## Security & Reliability

19. **Never Expose Sensitive Data in Query Keys**
    - Query keys should be serializable and safe to log.

20. **Use Retry Strategically**
    - Default retries may harm UX on destructive operations.
    - Configure retries per-query to fit the use case.

---

## Summary Checklist

- [ ] Queries = remote server state only
- [ ] Consistent query key strategy
- [ ] Encapsulate queries in reusable hooks
- [ ] Smart `staleTime` and `cacheTime` defaults
- [ ] Conditional queries with `enabled`
- [ ] Mutations use optimistic updates
- [ ] Intentional invalidation, no blind refetches
- [ ] Minimal global loading spinners
- [ ] Prefetch + hydration for SSR/SSG
- [ ] Proper test isolation with `QueryClient`
- [ ] Secure, serializable query keys

---

Follow these rules to keep your React Query setup clean, scalable, and production-ready.
React Query - Cursor IDE AI Rule