diff --git a/.agents/skills/tkdodo-react-query/SKILL.md b/.agents/skills/tkdodo-react-query/SKILL.md new file mode 100644 index 0000000000..56c8d80174 --- /dev/null +++ b/.agents/skills/tkdodo-react-query/SKILL.md @@ -0,0 +1,39 @@ +--- +name: tkdodo-react-query +description: TanStack Query / React Query implementation and review guidance distilled from TkDodo's 32-part Practical React Query series. Use when working on query keys, queryOptions factories, queryFn context, selectors, data transformations, status checks, loading/error UX, mutations, invalidation, optimistic updates, infinite queries, forms, React Router loaders/actions, cache seeding, WebSockets, offline behavior, testing, TypeScript inference, or deciding whether React Query is the right abstraction. +--- + +# TkDodo React Query + +## Intent + +Use this skill to apply TkDodo's React Query guidance without loading the full blog series into context. + +For Dify frontend work, combine this skill with `frontend-query-mutation`: let `frontend-query-mutation` govern local oRPC, contract, service-layer, and invalidation conventions; use this skill for general TanStack Query design judgment. + +## Workflow + +1. Identify the React Query concern. + - Read `references/practice-guide.md` for implementation and review rules. + - Read `references/series-index.md` when you need the original article mapping, source links, or deeper topic selection. +2. Preserve TypeScript inference. + - Prefer `queryOptions()` and colocated query factories over wrapper types that erase inference. + - Avoid manually passing `useQuery` generics unless inference cannot express the intended result. +3. Treat query keys as dependencies. + - Include every queryFn input in the key. + - Use stable array or object keys that support broad and targeted invalidation. +4. Choose the least surprising cache update strategy. + - Prefer invalidation after mutations. + - Use direct cache writes when the mutation response is the complete replacement for the cached entity. + - Use optimistic updates only when the UX benefit justifies duplicating server logic. +5. Review UX states deliberately. + - Keep previously available data visible during background refetches where possible. + - Do not replace good data with a full-page error for a background failure unless the product explicitly wants that. +6. Cite original sources when documenting rationale. + - Do not copy full article text into code comments or docs. + - Link to the relevant article from `references/series-index.md` for non-obvious decisions. + +## References + +- `references/practice-guide.md`: concise applied rules for implementation and code review. +- `references/series-index.md`: all 32 series entries, source URLs, and topic map. diff --git a/.agents/skills/tkdodo-react-query/agents/openai.yaml b/.agents/skills/tkdodo-react-query/agents/openai.yaml new file mode 100644 index 0000000000..efb81906bb --- /dev/null +++ b/.agents/skills/tkdodo-react-query/agents/openai.yaml @@ -0,0 +1,4 @@ +interface: + display_name: "TkDodo React Query" + short_description: "TanStack Query guidance distilled from TkDodo series" + default_prompt: "Use this skill when implementing or reviewing TanStack Query / React Query code, especially query keys, queryOptions, mutations, invalidation, selectors, optimistic updates, forms, testing, and TypeScript inference." diff --git a/.agents/skills/tkdodo-react-query/references/practice-guide.md b/.agents/skills/tkdodo-react-query/references/practice-guide.md new file mode 100644 index 0000000000..0988e4a38f --- /dev/null +++ b/.agents/skills/tkdodo-react-query/references/practice-guide.md @@ -0,0 +1,185 @@ +# TkDodo React Query Practice Guide + +This is an applied, paraphrased digest of TkDodo's Practical React Query series. It is not a copy of the articles. Use `series-index.md` for source links. + +## Table of Contents + +- Mental model +- Query keys +- Query options and abstractions +- Data transformation and selectors +- Status, loading, and error UX +- TypeScript and type safety +- Mutations and invalidation +- Optimistic updates +- Forms +- Router loaders and cache seeding +- Infinite queries +- Real-time and offline behavior +- Testing +- Review checklist + +## Mental Model + +- Treat React Query as an async state manager and data synchronization tool, not a normalized client database. +- Keep server state and client state separate. Do not copy query data into local state unless you intentionally take a snapshot, such as initial form values. +- Tune `staleTime` before reaching for manual refetching. Most freshness surprises are stale/fresh decisions, not cache lifetime decisions. +- Rarely change `gcTime` unless memory pressure or cache retention behavior is the actual problem. +- Prefer declarative data dependencies over imperative refetch chains. + +## Query Keys + +- Put every queryFn input in the query key. If a value changes the request, it belongs in the key. +- Query keys should be arrays or structured objects that allow partial matching. +- Do not reuse one key shape for finite and infinite queries. Infinite queries store pages and page params; finite queries store a different shape. +- Colocate query keys and query options with the feature that owns the data access. +- Use factories when multiple call sites need the same key/options or when invalidation depends on consistent prefixes. + +```ts +import { queryOptions } from '@tanstack/react-query' + +type ProjectListInput = { + workspaceId: string + keyword?: string +} + +export const projectQueries = { + all: () => ['projects'] as const, + lists: () => [...projectQueries.all(), 'list'] as const, + list: (input: ProjectListInput) => + queryOptions({ + queryKey: [...projectQueries.lists(), input] as const, + queryFn: ({ signal }) => fetchProjects(input, { signal }), + }), +} +``` + +## Query Options and Abstractions + +- Prefer `queryOptions()` for reusable options. It preserves the relationship between key, queryFn, and result type. +- Avoid broad wrapper hooks that accept most of `UseQueryOptions`; those abstractions often weaken inference and hide important behavior. +- If a custom hook is useful, make it narrow and domain-specific. +- Let call sites compose options that are truly local, such as `enabled`, `select`, or component-specific `staleTime`. +- In reusable libraries, accept options only after deciding which fields are safe to override. Avoid exposing overrides that can break cache identity or fetch behavior. + +```ts +export const useProjectList = (input: ProjectListInput) => { + return useQuery(projectQueries.list(input)) +} + +export const useProjectNames = (input: ProjectListInput) => { + return useQuery({ + ...projectQueries.list(input), + select: projects => projects.map(project => project.name), + }) +} +``` + +## Data Transformation and Selectors + +- Prefer backend/API transformation when it is the real contract shape. +- Transform in the `queryFn` when every consumer should see the transformed data and devtools should show the transformed shape. +- Use `select` for observer-specific projections and fine-grained subscriptions. +- Keep `select` stable if it is expensive or if it is passed through abstractions. Extract named functions or memoize carefully. +- Structural sharing helps avoid re-renders when data is equal by structure. Do not defeat it with unnecessary deep cloning. + +## Status, Loading, and Error UX + +- Distinguish first load from background refetch. `isPending` or lack of data usually means no usable result yet; `isFetching` can also mean background refresh. +- Prefer rendering stale data with a subtle refresh indicator over replacing it with a spinner. +- Check for available data before showing a hard error when background refetches can fail. +- Use Error Boundaries for renderable query errors when a full fallback is appropriate. +- Use global cache callbacks for cross-cutting notifications. Avoid duplicate toast logic at every observer. +- Remember that callbacks tied to an observer may not run if the observer unmounts before the mutation settles. + +## TypeScript and Type Safety + +- Prefer inference from typed API functions. Type the fetcher response, not every `useQuery` generic. +- Avoid "lying" angle brackets: specifying a generic does not validate runtime data. +- Validate untrusted API responses in the queryFn when runtime shape matters, for example with a schema library. +- Use narrowing from query state instead of destructuring in ways that lose correlation between `status`, `data`, and `error`. +- With dependent queries, use `enabled` for runtime gating, but still make the queryFn type-safe for unavailable inputs. +- Use `queryOptions()` so helpers like `getQueryData` can infer associated data types from the key where supported. + +## Mutations and Invalidation + +- Mutations are imperative server side effects. Queries are declarative subscriptions to server state. +- Prefer invalidating related queries after successful mutations. It is simple and robust when the server owns final truth. +- Await invalidation if the UI must stay pending until the refetch completes; otherwise let it happen in the background. +- Use direct `setQueryData` when the mutation response is the authoritative new cached value. +- Use global `MutationCache` callbacks when automatic invalidation should apply across the app. +- Use `mutationKey` or `meta` to connect mutations to invalidation scopes. +- Prefer `mutate` for UI event handlers. Use `mutateAsync` only when Promise composition is genuinely needed. +- Pass mutation variables as a single object to keep room for growth. + +```ts +const updateProject = useMutation({ + mutationKey: ['projects', 'update'], + mutationFn: (input: UpdateProjectInput) => api.updateProject(input), + onSuccess: (project) => { + queryClient.setQueryData(projectQueries.detail(project.id).queryKey, project) + return queryClient.invalidateQueries({ queryKey: projectQueries.lists() }) + }, +}) +``` + +## Optimistic Updates + +- Use optimistic updates for interactions where latency would be visibly harmful. +- Cancel in-flight queries that might overwrite the optimistic value. +- Snapshot previous cache state and return it from `onMutate` for rollback. +- Scope invalidation so concurrent optimistic mutations do not repeatedly overwrite each other. +- Avoid optimistic updates when the client would need to reimplement complex server logic, permissions, filtering, ranking, or derived fields. + +## Forms + +- A form usually starts from server state but becomes client state once the user edits it. +- The simple pattern is to load query data, initialize the form, and avoid background updates with an appropriate `staleTime`. +- If collaborative or long-lived forms need background updates, derive displayed values from server data plus dirty client fields rather than copying the whole query result. +- Disable double submits through mutation pending state. +- After successful mutation, invalidate affected queries and reset form state deliberately. + +## Router Loaders and Cache Seeding + +- React Router loaders are good for fetching early; React Query is better as the cache and synchronization layer. +- In loaders, prefer `getQueryData(...) ?? fetchQuery(...)` or `ensureQueryData(...)` so navigation can reuse cached data. +- In actions, invalidate the same query scopes the mutation would invalidate. +- Use cache seeding from list data to detail data when it avoids waterfalls and the list item is sufficient as initial detail data. +- Prefer pull seeding (`initialData` from an existing cache entry) when the detail query can derive from an already cached list. +- Prefer push seeding (`setQueryData` while fetching a list) only when you are comfortable writing multiple cache entries up front. + +## Infinite Queries + +- Infinite queries are one query with pages, not many independent page queries. +- The key identifies the whole infinite list; page params are managed separately. +- Refetching may need to replay pages to preserve cursor correctness. +- Keep `getNextPageParam` and `initialPageParam` explicit. +- Do not manually write page data unless you preserve `{ pages, pageParams }` shape. + +## Real-Time and Offline Behavior + +- For WebSockets, use messages as invalidation or partial cache update signals. Keep React Query as the cache owner. +- Prefer invalidating entity/list scopes from events unless the event payload contains enough data for a safe direct update. +- With push-driven updates, consider longer `staleTime` because the socket becomes the freshness trigger. +- Understand network mode before building offline behavior: some queries should pause offline, while others can read from local persistence or service worker caches. + +## Testing + +- Use a fresh `QueryClient` per test to avoid cache leakage. +- Wrap tested hooks/components in `QueryClientProvider`. +- Turn retries off in tests unless retry behavior is under test. +- Mock network at the boundary. MSW-style request mocks usually age better than mocking React Query itself. +- Await query results through UI or hook state, not arbitrary timers. +- Silence expected network errors in test output only when assertions cover the error path. + +## Review Checklist + +- Does every request input appear in the query key? +- Are finite and infinite query keys distinct? +- Is data kept in query cache instead of copied into local state without reason? +- Are loading and background fetching states handled separately? +- Are background errors displayed without destroying useful stale data? +- Does the abstraction preserve inference and avoid broad `UseQueryOptions` plumbing? +- Is invalidation scoped clearly after mutations? +- Is optimistic logic simpler than the server logic it approximates? +- Are tests isolated with a fresh `QueryClient` and retries disabled? diff --git a/.agents/skills/tkdodo-react-query/references/series-index.md b/.agents/skills/tkdodo-react-query/references/series-index.md new file mode 100644 index 0000000000..5b408a1dbb --- /dev/null +++ b/.agents/skills/tkdodo-react-query/references/series-index.md @@ -0,0 +1,161 @@ +# Practical React Query Series Index + +Fetched and summarized on 2026-04-30 from TkDodo's blog index: . The source index listed 32 parts. This file is a paraphrased lookup guide with links; it intentionally does not mirror the copyrighted article bodies. + +## Table of Contents + +- Article lookup +- Topic map +- Source handling + +## Article Lookup + +1. [Practical React Query](https://tkdodo.eu/blog/practical-react-query) + - Use for the core mental model: server state vs. client state, defaults, `staleTime` vs. `gcTime`, query keys as dependencies, `enabled`, and custom hooks. + - Prefer React Query cache for server state, not as a local state store. + +2. [React Query Data Transformations](https://tkdodo.eu/blog/react-query-data-transformations) + - Use when deciding where to reshape API data: backend, `queryFn`, render, or `select`. + - Pick the transformation layer based on whether every consumer or only one observer needs the shape. + +3. [React Query Render Optimizations](https://tkdodo.eu/blog/react-query-render-optimizations) + - Use for re-render concerns, tracked properties, `notifyOnChangeProps`, and structural sharing. + - Optimize after identifying a real render problem; keep structural sharing intact. + +4. [Status Checks in React Query](https://tkdodo.eu/blog/status-checks-in-react-query) + - Use when reviewing `isPending`, `isLoading`, `isError`, `isFetching`, and background error UX. + - Prefer showing existing data over replacing the screen with an error for a background failure. + +5. [Testing React Query](https://tkdodo.eu/blog/testing-react-query) + - Use for hook/component testing setup with `QueryClientProvider`, isolated clients, disabled retries, and awaited assertions. + - Mock network calls rather than mocking React Query internals. + +6. [React Query and TypeScript](https://tkdodo.eu/blog/react-query-and-type-script) + - Use for generics, inference, error typing, dependent queries, optimistic updates, infinite query typing, and default query functions. + - Prefer typed fetchers and inference over manual `useQuery` generic arguments. + +7. [Using WebSockets with React Query](https://tkdodo.eu/blog/using-web-sockets-with-react-query) + - Use when real-time messages should invalidate or update cached data. + - Consider longer `staleTime` when server push becomes the freshness source. + +8. [Effective React Query Keys](https://tkdodo.eu/blog/effective-react-query-keys) + - Use for key structure, colocated key factories, array keys, cache identity, and invalidation scopes. + - Never share keys between finite and infinite queries. + +9. [Leveraging the Query Function Context](https://tkdodo.eu/blog/leveraging-the-query-function-context) + - Use when query keys and query functions drift apart. + - Read request variables from `QueryFunctionContext` where it improves type safety and keeps dependencies explicit. + +10. [Placeholder and Initial Data in React Query](https://tkdodo.eu/blog/placeholder-and-initial-data-in-react-query) + - Use when avoiding loading flashes with `placeholderData` or `initialData`. + - Remember that initial data affects cache-level state, while placeholder data is observer-level. + +11. [React Query as a State Manager](https://tkdodo.eu/blog/react-query-as-a-state-manager) + - Use for explaining React Query as an async state manager and stale-while-revalidate tool. + - Tune `staleTime` for the product's freshness expectations instead of forcing manual sync. + +12. [React Query Error Handling](https://tkdodo.eu/blog/react-query-error-handling) + - Use for Error Boundaries, global query cache callbacks, toast notifications, and error propagation. + - Prefer global callbacks for cross-cutting notifications to avoid duplicate observer side effects. + +13. [Mastering Mutations in React Query](https://tkdodo.eu/blog/mastering-mutations-in-react-query) + - Use for mutation lifecycle, invalidation, direct updates, optimistic updates, `mutate` vs. `mutateAsync`, callback behavior, and mutation variables. + - Prefer invalidation unless the mutation response is sufficient for a precise cache write. + +14. [Offline React Query](https://tkdodo.eu/blog/offline-react-query) + - Use for network mode decisions and offline semantics. + - Decide whether a query requires the network, can run always, or should behave offline-first. + +15. [React Query and Forms](https://tkdodo.eu/blog/react-query-and-forms) + - Use when server state initializes editable form state. + - Choose between snapshotting initial data and keeping background updates with derived dirty fields. + +16. [React Query FAQs](https://tkdodo.eu/blog/react-query-fa-qs) + - Use for common debugging questions: refetch parameters, loading states, updates not showing, unstable clients, fetch API errors, and queryFns not running. + - Most cache-update bugs come from mismatched query keys or an unstable `QueryClient`. + +17. [React Query meets React Router](https://tkdodo.eu/blog/react-query-meets-react-router) + - Use when integrating loaders/actions with React Query. + - Fetch early in loaders, cache through React Query, and invalidate in actions. + +18. [Seeding the Query Cache](https://tkdodo.eu/blog/seeding-the-query-cache) + - Use for avoiding fetch waterfalls, especially list-to-detail transitions and Suspense. + - Seed detail queries from list data only when the list item is good enough as initial detail data. + +19. [Inside React Query](https://tkdodo.eu/blog/inside-react-query) + - Use for internal architecture: `QueryClient`, `QueryCache`, `Query`, `QueryObserver`, active and inactive queries. + - Helpful when debugging observer behavior or cache lifecycle. + +20. [Type-safe React Query](https://tkdodo.eu/blog/type-safe-react-query) + - Use for the difference between having TypeScript annotations and validating real API data. + - Add runtime validation in the queryFn when API trust is not enough. + +21. [You Might Not Need React Query](https://tkdodo.eu/blog/you-might-not-need-react-query) + - Use when deciding whether React Query is appropriate. + - React Query is most useful for client-owned data synchronization concerns; route/framework data APIs can cover simpler cases. + +22. [Thinking in React Query](https://tkdodo.eu/blog/thinking-in-react-query) + - Use for a high-level mindset talk: declarative dependencies, freshness, cache ownership, and server-state thinking. + - Useful when code is written as imperative fetch orchestration. + +23. [React Query and React Context](https://tkdodo.eu/blog/react-query-and-react-context) + - Use when Context is used to pass implicit query parameters or synchronize query data. + - Prefer explicit dependencies; beware request waterfalls and hidden coupling. + +24. [Why You Want React Query](https://tkdodo.eu/blog/why-you-want-react-query) + - Use to justify a data-fetching library over hand-written effects. + - Covers race conditions, loading/empty state handling, StrictMode double effects, cancellation, and error handling. + +25. [The Query Options API](https://tkdodo.eu/blog/the-query-options-api) + - Use for v5 `queryOptions`, type inference, data tags, and query factories. + - Prefer options factories to preserve type links between keys, fetchers, and cached data. + +26. [Automatic Query Invalidation after Mutations](https://tkdodo.eu/blog/automatic-query-invalidation-after-mutations) + - Use for app-level invalidation with global mutation cache callbacks. + - Scope automatic invalidation with `mutationKey`, `meta`, `staleTime`, and awaited vs. background invalidation choices. + +27. [How Infinite Queries work](https://tkdodo.eu/blog/how-infinite-queries-work) + - Use for infinite query internals, `QueryBehavior`, retry behavior, and page refetch architecture. + - Preserve `{ pages, pageParams }` shape and cursor correctness. + +28. [React Query API Design - Lessons Learned](https://tkdodo.eu/blog/react-query-api-design-lessons-learned) + - Use for API design tradeoffs: overloads, object syntax, naming, DX, migration, and maintainability. + - Useful when designing local abstractions over React Query. + +29. [React Query - The Bad Parts](https://tkdodo.eu/blog/react-query-the-bad-parts) + - Use for tradeoff analysis and cases where React Query can be too much. + - Good review prompt for bundle cost, abstraction cost, mental-model complexity, and alternative framework data APIs. + +30. [Concurrent Optimistic Updates in React Query](https://tkdodo.eu/blog/concurrent-optimistic-updates-in-react-query) + - Use for race-resistant optimistic UI when multiple mutations affect the same cached entity or list. + - Cancel conflicting queries and prevent over-invalidation windows. + +31. [React Query Selectors, Supercharged](https://tkdodo.eu/blog/react-query-selectors-supercharged) + - Use for `select`, fine-grained subscriptions, query hash behavior, memoization, and selector typing. + - Stabilize expensive selectors and preserve result inference. + +32. [Creating Query Abstractions](https://tkdodo.eu/blog/creating-query-abstractions) + - Use for building reusable abstractions around inference-heavy APIs. + - Prefer `queryOptions()` composition over passing broad `UseQueryOptions` through custom hooks. + +## Topic Map + +- Core mindset: 1, 11, 21, 22, 24, 29 +- Keys and query function inputs: 1, 8, 9, 16, 25 +- TypeScript and inference: 6, 20, 25, 31, 32 +- Data shape and render performance: 2, 3, 31 +- Loading, status, and errors: 4, 10, 12, 16 +- Mutations and invalidation: 13, 26, 30 +- Optimistic updates: 6, 13, 30 +- Forms: 15 +- Router integration and cache seeding: 17, 18, 23 +- Infinite queries: 6, 27 +- Real-time and offline: 7, 14 +- Testing: 5 +- Internals and API design: 19, 27, 28 + +## Source Handling + +- Link to the original article when adding durable rationale to project docs or comments. +- Quote at most a short phrase if needed; otherwise paraphrase. +- Re-check the source index if the user asks for the latest series contents.