This commit is contained in:
Stephen Zhou 2026-05-08 14:54:49 +08:00
parent 2212384a35
commit a2698a1c00
No known key found for this signature in database
5 changed files with 39 additions and 389 deletions

View File

@ -0,0 +1,39 @@
---
name: how-to-write-component
description: React/TypeScript component style guide. Use when writing, refactoring, or reviewing React components, especially around props typing, state boundaries, shared local state with Jotai atoms, API types, navigation, memoization, wrappers, and empty-state handling.
---
# How To Write A Component
Follow existing project patterns first. Use these rules to resolve unclear component decisions:
## Component Declaration And Exports
- Do not use `FC` or `React.FC`; type the function signature directly.
- Prefer `function` for top-level components and module helpers. Arrow functions are fine for local callbacks, handlers, and APIs that naturally take lambdas.
- Prefer named exports. Use default exports only where the framework requires them, such as Next.js route files.
## Props And API Types
- Type simple one-off props inline. Use a named `Props` type only when reused, exported, complex, or clearer.
- Prefer API-generated or API-returned types at component boundaries. Keep small UI conversion helpers beside the component that needs them.
- Avoid duplicate invariant checks across parent and child. If a lower-level component already handles empty or invalid data, let callers pass raw values through and keep the fallback there.
## State Ownership
- Keep state, query state, handlers, and derived UI data near the component that owns the interaction. Do not lift state unless siblings or parents genuinely coordinate through it.
- When local UI state must be shared across siblings, distant children, or feature-local surfaces, use a colocated Jotai `atom` instead of prop drilling or lifting state into a broad parent. Keep atoms feature-scoped and UI-owned; do not use them for server/cache state that belongs in query or API data flow.
- Prefer uncontrolled components when DOM-owned state is enough. Expose style customization through CSS variables before adding controlled props only for visual changes.
## Component Boundaries
- Keep the component's first-render surface separate from secondary interactive surfaces. For dialogs, dropdown menus, popovers, and similar branches, split from the trigger boundary: extract a small local component that owns the trigger, open state, and overlay/menu content when that branch would obscure the parent UI flow. Do not further split the dialog body, menu body, or form content unless it has its own state, reuse, complexity, or semantic boundary.
- Avoid shallow wrappers and unnecessary renaming. Call the original function directly unless the wrapper adds validation, orchestration, error handling, state ownership, or a real semantic boundary.
## Navigation
- Prefer `Link` for normal navigation. Use router APIs only for command-flow side effects such as mutation success, guarded redirects, or form submission.
## Performance
- Avoid `memo`, `useMemo`, and `useCallback` unless there is a clear performance reason.

View File

@ -1,39 +0,0 @@
---
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

@ -1,4 +0,0 @@
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

@ -1,185 +0,0 @@
# 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

@ -1,161 +0,0 @@
# 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.