add skill

This commit is contained in:
Stephen Zhou 2026-04-30 15:48:11 +08:00
parent 48a96739d4
commit 396c349cdd
No known key found for this signature in database
4 changed files with 389 additions and 0 deletions

View File

@ -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.

View File

@ -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."

View File

@ -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?

View File

@ -0,0 +1,161 @@
# Practical React Query Series Index
Fetched and summarized on 2026-04-30 from TkDodo's blog index: <https://tkdodo.eu/blog/practical-react-query>. 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.