@jgamaraalv/ts-dev-kit 2.2.0 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +3 -3
- package/.claude-plugin/plugin.json +2 -2
- package/CHANGELOG.md +52 -0
- package/README.md +106 -50
- package/package.json +2 -2
- package/skills/codebase-adapter/SKILL.md +237 -0
- package/skills/codebase-adapter/template.md +25 -0
- package/skills/conventional-commits/SKILL.md +12 -0
- package/skills/core-web-vitals/SKILL.md +102 -0
- package/skills/core-web-vitals/references/cls.md +154 -0
- package/skills/core-web-vitals/references/inp.md +140 -0
- package/skills/core-web-vitals/references/lcp.md +89 -0
- package/skills/core-web-vitals/references/tools.md +112 -0
- package/skills/core-web-vitals/scripts/visualize.py +222 -0
- package/skills/debug/SKILL.md +10 -27
- package/skills/debug/template.md +23 -0
- package/skills/{task → execute-task}/SKILL.md +78 -50
- package/skills/generate-prd/SKILL.md +56 -0
- package/skills/generate-prd/template.md +69 -0
- package/skills/generate-task/SKILL.md +136 -0
- package/skills/generate-task/template.md +71 -0
- package/skills/owasp-security-review/SKILL.md +1 -10
- package/skills/owasp-security-review/template.md +6 -0
- package/skills/tanstack-query/SKILL.md +348 -0
- package/skills/tanstack-query/references/advanced-patterns.md +376 -0
- package/skills/tanstack-query/references/api-reference.md +297 -0
- package/skills/tanstack-query/references/ssr-nextjs.md +272 -0
- package/skills/tanstack-query/references/testing.md +175 -0
- package/skills/ui-ux-guidelines/SKILL.md +1 -17
- package/skills/ui-ux-guidelines/template.md +15 -0
- /package/skills/{task → execute-task}/references/agent-dispatch.md +0 -0
- /package/skills/{task → execute-task}/references/verification-protocol.md +0 -0
- /package/skills/{task/references/output-templates.md → execute-task/template.md} +0 -0
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: generate-task
|
|
3
|
+
description: "Breaks a PRD into ordered, production-ready engineering tasks ready for execution by /execute-task. Use when: (1) converting a PRD document into executable engineering tasks, (2) planning feature delivery as a sequence of mergeable, self-contained pull requests, (3) the user says 'generate tasks', 'break down this PRD', 'create tasks from PRD', 'plan the implementation tasks', or 'task breakdown'. Each generated task document embeds its own success criteria, baseline checks, post-change tests, performance benchmarks, and non-functional requirements — making it directly executable by /execute-task."
|
|
4
|
+
argument-hint: "[prd-file-path | task-without-context | epic-task | big-task]"
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
<prd>
|
|
8
|
+
$ARGUMENTS
|
|
9
|
+
</prd>
|
|
10
|
+
|
|
11
|
+
<workflow>
|
|
12
|
+
Follow each phase in order.
|
|
13
|
+
|
|
14
|
+
<phase_1_read_prd>
|
|
15
|
+
Read the PRD document fully. Extract and organize:
|
|
16
|
+
- Functional requirements — numbered, atomic conditions
|
|
17
|
+
- Acceptance criteria — how feature completion is verified
|
|
18
|
+
- Non-functional requirements — performance, security, accessibility, scalability targets
|
|
19
|
+
- Scope — what is included and what is explicitly excluded
|
|
20
|
+
- User journeys — key flows and their steps
|
|
21
|
+
- Success metrics and KPIs
|
|
22
|
+
</phase_1_read_prd>
|
|
23
|
+
|
|
24
|
+
<phase_2_analyze_codebase>
|
|
25
|
+
Before decomposing tasks, understand the target project:
|
|
26
|
+
1. Read CLAUDE.md and root package.json — project structure, package manager, tech stack, key directories.
|
|
27
|
+
2. Identify where implementation units live — backend routes, frontend pages, shared types, database schemas, tests.
|
|
28
|
+
3. Search for patterns similar to the feature being built — use Grep/Glob to find related files and establish co-location conventions.
|
|
29
|
+
4. List the domain areas the feature touches — database, API, shared, frontend, tests, config.
|
|
30
|
+
</phase_2_analyze_codebase>
|
|
31
|
+
|
|
32
|
+
<phase_3_identify_implementation_units>
|
|
33
|
+
Map every PRD requirement to concrete implementation units. An implementation unit is any atomic change: a new schema, a route, a component, a migration, a shared type, a test file, a config entry, an i18n key.
|
|
34
|
+
|
|
35
|
+
For each unit, identify:
|
|
36
|
+
- **File path** (exact, following codebase conventions)
|
|
37
|
+
- **Action**: create or modify
|
|
38
|
+
- **Domain**: database | api | shared | frontend | test | config | docs
|
|
39
|
+
- **Dependencies**: which other units must exist first
|
|
40
|
+
|
|
41
|
+
**Standard dependency ordering** (lower layers before higher):
|
|
42
|
+
1. Shared types, constants, i18n keys, env variables
|
|
43
|
+
2. Database migrations and schema updates
|
|
44
|
+
3. API routes, handlers, validation schemas
|
|
45
|
+
4. Shared hooks, utilities, helper functions
|
|
46
|
+
5. UI components (atoms → molecules → organisms)
|
|
47
|
+
6. Pages and routes composing components
|
|
48
|
+
7. Tests (unit, integration, E2E)
|
|
49
|
+
8. Config and infrastructure changes
|
|
50
|
+
9. Documentation updates
|
|
51
|
+
|
|
52
|
+
**Orphan-free rule** — every consumer of a resource must be in the same task as its producer OR in a later task that explicitly depends on the producer's task:
|
|
53
|
+
- New i18n key + every component using that key → same task (or key in TASK_N, component in TASK_M where M > N and TASK_M depends on TASK_N)
|
|
54
|
+
- New database column + migration that adds it → same task
|
|
55
|
+
- New shared type + every immediate consumer → same task
|
|
56
|
+
- New component + the page that renders it → same task (unless page is intentionally deferred to a later task)
|
|
57
|
+
</phase_3_identify_implementation_units>
|
|
58
|
+
|
|
59
|
+
<phase_4_group_into_tasks>
|
|
60
|
+
Group implementation units into tasks. Apply these rules in order:
|
|
61
|
+
|
|
62
|
+
**Rule 1 — 30-file limit**: a task may create or modify at most 30 files. If a natural group exceeds this, split on domain boundaries (data layer, API layer, UI layer, test layer).
|
|
63
|
+
|
|
64
|
+
**Rule 2 — Production-ready delivery**: every task, when merged in order, must leave the application in a runnable state — no broken imports, unresolved references, orphaned i18n keys, or missing migrations.
|
|
65
|
+
|
|
66
|
+
**Rule 3 — Forward dependency only**: if TASK_N requires output from TASK_M, then M < N. No task may depend on a later task.
|
|
67
|
+
|
|
68
|
+
**Rule 4 — Mergeable without breaking**: use feature flags, graceful degradation, or empty-state handling so earlier tasks don't expose incomplete UX to end users.
|
|
69
|
+
|
|
70
|
+
**Rule 5 — Clear value delivery**: each task must deliver a demonstrable increment — a working endpoint, a rendered component, a passing test suite. Avoid tasks with no visible or testable outcome.
|
|
71
|
+
|
|
72
|
+
**Recommended grouping** (adapt per feature):
|
|
73
|
+
1. **Foundation** — shared types, constants, i18n keys, env variables
|
|
74
|
+
2. **Data layer** — database schema, migrations, ORM models
|
|
75
|
+
3. **API layer** — routes, handlers, validation schemas, error codes
|
|
76
|
+
4. **Core UI** — reusable components, hooks, state management
|
|
77
|
+
5. **Feature pages** — pages and routes composing the core UI
|
|
78
|
+
6. **Tests & polish** — comprehensive test suites, accessibility audit, performance tuning
|
|
79
|
+
7. **Documentation** — CLAUDE.md updates, API docs, migration guides
|
|
80
|
+
|
|
81
|
+
Split tasks at domain boundaries when a group would exceed 30 files.
|
|
82
|
+
</phase_4_group_into_tasks>
|
|
83
|
+
|
|
84
|
+
<phase_5_define_verification_criteria>
|
|
85
|
+
For each task, derive its verification criteria from the PRD. These become binding requirements embedded in the task document and executed by /execute-task.
|
|
86
|
+
|
|
87
|
+
**Success criteria** — select PRD acceptance criteria that apply to this task's scope. Write them as testable assertions:
|
|
88
|
+
- "POST /api/resource returns 201 with the created resource payload" (not "API works")
|
|
89
|
+
- "Page renders the empty state at 1440px without console errors" (not "page looks right")
|
|
90
|
+
- "Migration runs cleanly on an empty database" (not "migration works")
|
|
91
|
+
|
|
92
|
+
**Baseline checks** — what to capture BEFORE making changes:
|
|
93
|
+
- Standard quality gates: tsc, lint, test, build (pass/fail and counts)
|
|
94
|
+
- Domain-specific: API endpoints (HTTP status + timing), pages (screenshot + LCP), schema state (table columns and types)
|
|
95
|
+
|
|
96
|
+
**Post-change checks** — what to verify AFTER changes, mapped 1:1 to each success criterion.
|
|
97
|
+
|
|
98
|
+
**Performance benchmarks** — from PRD NFRs or domain defaults:
|
|
99
|
+
- API endpoints: p95 response time target
|
|
100
|
+
- Frontend pages: LCP target, bundle size delta
|
|
101
|
+
- Database queries: execution time target
|
|
102
|
+
|
|
103
|
+
**Non-functional requirements** — scope PRD NFRs to this task's domain:
|
|
104
|
+
- Database task → data integrity, migration rollback safety, index strategy
|
|
105
|
+
- API task → input validation coverage, auth guard presence, rate limiting
|
|
106
|
+
- Frontend task → WCAG compliance level, responsive breakpoints, keyboard navigation
|
|
107
|
+
</phase_5_define_verification_criteria>
|
|
108
|
+
|
|
109
|
+
<phase_6_generate_task_documents>
|
|
110
|
+
Generate a document for each task using the template from [template.md](template.md).
|
|
111
|
+
|
|
112
|
+
Before saving, validate each document:
|
|
113
|
+
- [ ] File count ≤ 30
|
|
114
|
+
- [ ] No file path appears in more than one task
|
|
115
|
+
- [ ] Every success criterion is testable (specific, measurable outcome)
|
|
116
|
+
- [ ] Every "create" file has its consumer in the same or a later task
|
|
117
|
+
- [ ] Task N's dependencies all have numbers < N
|
|
118
|
+
- [ ] Baseline checks include at minimum: tsc, lint, test, build
|
|
119
|
+
|
|
120
|
+
Fix any violation before saving.
|
|
121
|
+
</phase_6_generate_task_documents>
|
|
122
|
+
</workflow>
|
|
123
|
+
|
|
124
|
+
<output>
|
|
125
|
+
Save each task document to:
|
|
126
|
+
```
|
|
127
|
+
[project-root]/docs/features/[FEATURE_NAME]/TASK_[TASK_NUMBER].md
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
After saving all tasks, print a summary table:
|
|
131
|
+
|
|
132
|
+
| Task | Title | Files | Depends on | Key deliverable |
|
|
133
|
+
|------|-------|-------|------------|-----------------|
|
|
134
|
+
| TASK_01 | ... | N files | none | ... |
|
|
135
|
+
| TASK_02 | ... | N files | TASK_01 | ... |
|
|
136
|
+
</output>
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# TASK_[N]: [Title]
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
[2-3 sentences describing what this task delivers and why it is a cohesive unit
|
|
5
|
+
of work. State the concrete value delivered when this task is merged.]
|
|
6
|
+
|
|
7
|
+
## Dependencies
|
|
8
|
+
- Requires merged: [list of TASK_X titles, or "none"]
|
|
9
|
+
|
|
10
|
+
## Scope — Files (max 30)
|
|
11
|
+
|
|
12
|
+
| File | Action | Purpose |
|
|
13
|
+
|------|--------|---------|
|
|
14
|
+
| `path/to/file.ts` | create | What this file contains |
|
|
15
|
+
| `path/to/other.ts` | modify | What changes and why |
|
|
16
|
+
|
|
17
|
+
**Total: N files**
|
|
18
|
+
|
|
19
|
+
## Success Criteria
|
|
20
|
+
|
|
21
|
+
- [ ] 1. [Testable, specific condition — maps to a PRD acceptance criterion]
|
|
22
|
+
- [ ] 2. [...]
|
|
23
|
+
|
|
24
|
+
## Non-functional Requirements
|
|
25
|
+
|
|
26
|
+
- **Performance**: [specific targets, e.g., "p95 API response < 200ms", "LCP < 2.5s"]
|
|
27
|
+
- **Security**: [e.g., "all inputs validated with zod schema", "auth guard on all routes"]
|
|
28
|
+
- **Accessibility**: [for frontend tasks: WCAG level and specific requirements]
|
|
29
|
+
- **Scalability**: [relevant requirements, or "n/a for this task"]
|
|
30
|
+
|
|
31
|
+
## Verification Plan
|
|
32
|
+
|
|
33
|
+
### Baseline Checks (run before making changes)
|
|
34
|
+
|
|
35
|
+
**Quality gates:**
|
|
36
|
+
- `tsc` — record pass/fail
|
|
37
|
+
- `lint` — record pass/fail
|
|
38
|
+
- `test` — record pass/fail + test count
|
|
39
|
+
- `build` — record pass/fail + bundle size (if frontend)
|
|
40
|
+
|
|
41
|
+
**Domain-specific baseline:**
|
|
42
|
+
- [Example — API: `GET /api/resource` — record HTTP status and response time]
|
|
43
|
+
- [Example — Frontend: navigate to `/feature-page` — capture screenshot and LCP]
|
|
44
|
+
- [Example — Database: record current schema of table `users` (columns + types)]
|
|
45
|
+
|
|
46
|
+
### Post-change Checks
|
|
47
|
+
|
|
48
|
+
**Quality gates:** [same commands as baseline — all must pass]
|
|
49
|
+
|
|
50
|
+
**Feature verification:**
|
|
51
|
+
- [ ] [Check mapped 1:1 to success criterion 1 — describe exact verification steps]
|
|
52
|
+
- [ ] [Check mapped 1:1 to success criterion 2]
|
|
53
|
+
|
|
54
|
+
**Performance benchmarks:**
|
|
55
|
+
- [metric]: target [value], acceptable range [range]
|
|
56
|
+
|
|
57
|
+
### MCP Checks
|
|
58
|
+
|
|
59
|
+
**If browser MCPs available (playwright / chrome-devtools):**
|
|
60
|
+
- [Specific pages to navigate and screenshot]
|
|
61
|
+
- [Specific user interactions to exercise]
|
|
62
|
+
- [Specific performance traces to capture]
|
|
63
|
+
|
|
64
|
+
**If no browser MCPs available (shell-only fallback):**
|
|
65
|
+
- [What to verify via build output, curl, or CLI commands]
|
|
66
|
+
|
|
67
|
+
## Implementation Notes
|
|
68
|
+
|
|
69
|
+
[Optional: key constraints, patterns to follow, architectural decisions the
|
|
70
|
+
executing agent should know. Reference specific file paths or conventions
|
|
71
|
+
discovered from the codebase analysis.]
|
|
@@ -86,13 +86,4 @@ Use these severity levels when reporting findings:
|
|
|
86
86
|
|
|
87
87
|
## Output format
|
|
88
88
|
|
|
89
|
-
When reporting security findings, use
|
|
90
|
-
|
|
91
|
-
```
|
|
92
|
-
### [SEVERITY] A0X: Category Name — Brief title
|
|
93
|
-
|
|
94
|
-
**Location**: `file:line`
|
|
95
|
-
**Risk**: What can go wrong and the impact.
|
|
96
|
-
**Finding**: What the code does wrong.
|
|
97
|
-
**Fix**: Specific remediation with code example.
|
|
98
|
-
```
|
|
89
|
+
When reporting security findings, use the template in [template.md](template.md) for each finding.
|
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: tanstack-query
|
|
3
|
+
description: |
|
|
4
|
+
TanStack Query v5 (React Query) reference for data fetching, caching,
|
|
5
|
+
and server state management in React. Use when: (1) writing useQuery,
|
|
6
|
+
useMutation, or useInfiniteQuery hooks, (2) setting up QueryClient
|
|
7
|
+
and queryOptions, (3) implementing optimistic updates or cache
|
|
8
|
+
invalidation, (4) configuring SSR/hydration with Next.js App Router
|
|
9
|
+
or Pages Router, (5) testing React Query hooks, (6) working with
|
|
10
|
+
TypeScript types, Suspense, or advanced patterns like dependent
|
|
11
|
+
queries and infinite scroll.
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
# TanStack Query v5 (React)
|
|
15
|
+
|
|
16
|
+
## Setup
|
|
17
|
+
|
|
18
|
+
```tsx
|
|
19
|
+
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
|
|
20
|
+
|
|
21
|
+
const queryClient = new QueryClient({
|
|
22
|
+
defaultOptions: {
|
|
23
|
+
queries: {
|
|
24
|
+
staleTime: 60 * 1000, // 1 min (default is 0)
|
|
25
|
+
gcTime: 5 * 60 * 1000, // 5 min (default)
|
|
26
|
+
retry: 3, // 3 retries with exponential backoff (default)
|
|
27
|
+
refetchOnWindowFocus: true, // default
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
function App() {
|
|
33
|
+
return (
|
|
34
|
+
<QueryClientProvider client={queryClient}>
|
|
35
|
+
<YourApp />
|
|
36
|
+
</QueryClientProvider>
|
|
37
|
+
)
|
|
38
|
+
}
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Important Defaults
|
|
42
|
+
|
|
43
|
+
| Default | Value | Notes |
|
|
44
|
+
|---------|-------|-------|
|
|
45
|
+
| `staleTime` | `0` | Cached data is immediately stale; triggers background refetch on mount/focus/reconnect |
|
|
46
|
+
| `gcTime` | `5 min` | Inactive queries garbage collected after 5 minutes |
|
|
47
|
+
| `retry` | `3` (queries) / `0` (mutations) | Queries retry 3x with exponential backoff; mutations do NOT retry |
|
|
48
|
+
| `refetchOnWindowFocus` | `true` | Stale queries refetch when tab regains focus |
|
|
49
|
+
| `refetchOnReconnect` | `true` | Stale queries refetch when network reconnects |
|
|
50
|
+
| `refetchOnMount` | `true` | Stale queries refetch when new instance mounts |
|
|
51
|
+
| `structuralSharing` | `true` | Preserves referential identity if data is structurally equal |
|
|
52
|
+
|
|
53
|
+
**Key recommendation:** Set `staleTime` above 0 to control refetch frequency rather than disabling individual refetch triggers.
|
|
54
|
+
|
|
55
|
+
## queryOptions — co-locate key + fn
|
|
56
|
+
|
|
57
|
+
Always use `queryOptions` to define query configurations. It enables type inference across `useQuery`, `prefetchQuery`, `getQueryData`, and `setQueryData`.
|
|
58
|
+
|
|
59
|
+
```tsx
|
|
60
|
+
import { queryOptions, infiniteQueryOptions } from '@tanstack/react-query'
|
|
61
|
+
|
|
62
|
+
export function todosOptions(filters: TodoFilters) {
|
|
63
|
+
return queryOptions({
|
|
64
|
+
queryKey: ['todos', filters],
|
|
65
|
+
queryFn: () => fetchTodos(filters),
|
|
66
|
+
staleTime: 5 * 1000,
|
|
67
|
+
})
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Works everywhere with full type inference:
|
|
71
|
+
useQuery(todosOptions({ status: 'done' }))
|
|
72
|
+
useSuspenseQuery(todosOptions({ status: 'done' }))
|
|
73
|
+
queryClient.prefetchQuery(todosOptions({ status: 'done' }))
|
|
74
|
+
queryClient.setQueryData(todosOptions({ status: 'done' }).queryKey, newData)
|
|
75
|
+
const cached = queryClient.getQueryData(todosOptions({ status: 'done' }).queryKey)
|
|
76
|
+
// ^? TodoItem[] | undefined
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
For infinite queries, use `infiniteQueryOptions` (same pattern, adds `initialPageParam` and `getNextPageParam`).
|
|
80
|
+
|
|
81
|
+
## useQuery
|
|
82
|
+
|
|
83
|
+
```tsx
|
|
84
|
+
const {
|
|
85
|
+
data, // TData | undefined
|
|
86
|
+
error, // TError | null
|
|
87
|
+
status, // 'pending' | 'error' | 'success'
|
|
88
|
+
isPending, // no cached data yet
|
|
89
|
+
isError,
|
|
90
|
+
isSuccess,
|
|
91
|
+
isFetching, // queryFn is running (including background refetch)
|
|
92
|
+
isLoading, // isPending && isFetching (first load only)
|
|
93
|
+
isPlaceholderData, // showing placeholder, not real data
|
|
94
|
+
isStale,
|
|
95
|
+
refetch,
|
|
96
|
+
fetchStatus, // 'fetching' | 'paused' | 'idle'
|
|
97
|
+
} = useQuery({
|
|
98
|
+
queryKey: ['todos', userId], // unique cache key (Array)
|
|
99
|
+
queryFn: () => fetchTodos(userId),
|
|
100
|
+
enabled: !!userId, // disable until userId exists
|
|
101
|
+
staleTime: 60_000,
|
|
102
|
+
select: (data) => data.filter(t => !t.done), // transform/filter
|
|
103
|
+
placeholderData: keepPreviousData, // smooth pagination
|
|
104
|
+
})
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
**Query states:** `status` tells you "do we have data?"; `fetchStatus` tells you "is the queryFn running?". They are orthogonal — a query can be `pending` + `paused` (no data, no network).
|
|
108
|
+
|
|
109
|
+
## Query Keys
|
|
110
|
+
|
|
111
|
+
Keys must be Arrays. They are hashed deterministically.
|
|
112
|
+
|
|
113
|
+
```tsx
|
|
114
|
+
// Object key order does NOT matter — these are equivalent:
|
|
115
|
+
useQuery({ queryKey: ['todos', { status, page }] })
|
|
116
|
+
useQuery({ queryKey: ['todos', { page, status }] })
|
|
117
|
+
|
|
118
|
+
// Array item order DOES matter — these are different:
|
|
119
|
+
useQuery({ queryKey: ['todos', status, page] })
|
|
120
|
+
useQuery({ queryKey: ['todos', page, status] })
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
**Rule:** If your queryFn depends on a variable, include it in the queryKey. The key acts as a dependency array.
|
|
124
|
+
|
|
125
|
+
## useMutation
|
|
126
|
+
|
|
127
|
+
```tsx
|
|
128
|
+
const queryClient = useQueryClient()
|
|
129
|
+
|
|
130
|
+
const mutation = useMutation({
|
|
131
|
+
mutationFn: (newTodo: CreateTodoInput) => api.post('/todos', newTodo),
|
|
132
|
+
onSuccess: (data, variables) => {
|
|
133
|
+
// Invalidate related queries to trigger refetch
|
|
134
|
+
queryClient.invalidateQueries({ queryKey: ['todos'] })
|
|
135
|
+
// Or update cache directly with response data
|
|
136
|
+
queryClient.setQueryData(['todos', data.id], data)
|
|
137
|
+
},
|
|
138
|
+
onError: (error, variables, onMutateResult) => {},
|
|
139
|
+
onSettled: (data, error, variables, onMutateResult) => {},
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
// Trigger:
|
|
143
|
+
mutation.mutate({ title: 'New todo' })
|
|
144
|
+
|
|
145
|
+
// Or with per-call callbacks:
|
|
146
|
+
mutation.mutate(input, { onSuccess: () => navigate('/todos') })
|
|
147
|
+
|
|
148
|
+
// Async variant (returns Promise):
|
|
149
|
+
const data = await mutation.mutateAsync(input)
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
**Lifecycle:** `onMutate` → `mutationFn` → `onSuccess`/`onError` → `onSettled`. Callbacks returning promises are awaited.
|
|
153
|
+
|
|
154
|
+
**Gotcha:** Per-call `mutate()` callbacks only fire for the *latest* call if mutations overlap. Use `useMutation`-level callbacks for reliable logic.
|
|
155
|
+
|
|
156
|
+
## Query Invalidation
|
|
157
|
+
|
|
158
|
+
```tsx
|
|
159
|
+
// Prefix match (default) — invalidates ['todos'] and ['todos', { page: 1 }]
|
|
160
|
+
queryClient.invalidateQueries({ queryKey: ['todos'] })
|
|
161
|
+
|
|
162
|
+
// Exact match only
|
|
163
|
+
queryClient.invalidateQueries({ queryKey: ['todos'], exact: true })
|
|
164
|
+
|
|
165
|
+
// Predicate for fine-grained control
|
|
166
|
+
queryClient.invalidateQueries({
|
|
167
|
+
predicate: (query) => query.queryKey[0] === 'todos' && query.queryKey[1]?.version >= 10,
|
|
168
|
+
})
|
|
169
|
+
|
|
170
|
+
// ALL queries
|
|
171
|
+
queryClient.invalidateQueries()
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
Invalidation marks queries as stale and triggers background refetch for active (rendered) queries.
|
|
175
|
+
|
|
176
|
+
## Optimistic Updates
|
|
177
|
+
|
|
178
|
+
### Approach 1: Via the UI (simpler, recommended)
|
|
179
|
+
|
|
180
|
+
Render optimistic state from `variables` directly in JSX:
|
|
181
|
+
|
|
182
|
+
```tsx
|
|
183
|
+
const { mutate, variables, isPending, isError } = useMutation({
|
|
184
|
+
mutationFn: (text: string) => api.post('/todos', { text }),
|
|
185
|
+
onSettled: () => queryClient.invalidateQueries({ queryKey: ['todos'] }),
|
|
186
|
+
})
|
|
187
|
+
|
|
188
|
+
// In JSX:
|
|
189
|
+
{todos.map(todo => <li key={todo.id}>{todo.text}</li>)}
|
|
190
|
+
{isPending && <li style={{ opacity: 0.5 }}>{variables}</li>}
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
Access pending mutations from other components with `useMutationState`:
|
|
194
|
+
|
|
195
|
+
```tsx
|
|
196
|
+
const pendingTodos = useMutationState<string>({
|
|
197
|
+
filters: { mutationKey: ['addTodo'], status: 'pending' },
|
|
198
|
+
select: (mutation) => mutation.state.variables,
|
|
199
|
+
})
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
### Approach 2: Via cache (with rollback)
|
|
203
|
+
|
|
204
|
+
```tsx
|
|
205
|
+
useMutation({
|
|
206
|
+
mutationFn: updateTodo,
|
|
207
|
+
onMutate: async (newTodo, context) => {
|
|
208
|
+
await context.client.cancelQueries({ queryKey: ['todos'] })
|
|
209
|
+
const previous = context.client.getQueryData(['todos'])
|
|
210
|
+
context.client.setQueryData(['todos'], (old) => [...old, newTodo])
|
|
211
|
+
return { previous }
|
|
212
|
+
},
|
|
213
|
+
onError: (err, newTodo, onMutateResult, context) => {
|
|
214
|
+
context.client.setQueryData(['todos'], onMutateResult.previous)
|
|
215
|
+
},
|
|
216
|
+
onSettled: (data, error, variables, onMutateResult, context) => {
|
|
217
|
+
context.client.invalidateQueries({ queryKey: ['todos'] })
|
|
218
|
+
},
|
|
219
|
+
})
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
**Always** `cancelQueries` before optimistic update to prevent background refetches from overwriting.
|
|
223
|
+
|
|
224
|
+
## Infinite Queries
|
|
225
|
+
|
|
226
|
+
```tsx
|
|
227
|
+
const {
|
|
228
|
+
data, // { pages: T[], pageParams: unknown[] }
|
|
229
|
+
fetchNextPage,
|
|
230
|
+
fetchPreviousPage,
|
|
231
|
+
hasNextPage, // true when getNextPageParam returns non-null/undefined
|
|
232
|
+
hasPreviousPage,
|
|
233
|
+
isFetchingNextPage,
|
|
234
|
+
isFetchingPreviousPage,
|
|
235
|
+
} = useInfiniteQuery({
|
|
236
|
+
queryKey: ['projects'],
|
|
237
|
+
queryFn: ({ pageParam }) => fetchProjects(pageParam),
|
|
238
|
+
initialPageParam: 0, // REQUIRED
|
|
239
|
+
getNextPageParam: (lastPage, allPages) => lastPage.nextCursor ?? undefined,
|
|
240
|
+
maxPages: 3, // optional: cap cached pages
|
|
241
|
+
})
|
|
242
|
+
|
|
243
|
+
// Render all pages:
|
|
244
|
+
{data.pages.map((page, i) => (
|
|
245
|
+
<Fragment key={i}>
|
|
246
|
+
{page.items.map(item => <div key={item.id}>{item.name}</div>)}
|
|
247
|
+
</Fragment>
|
|
248
|
+
))}
|
|
249
|
+
|
|
250
|
+
// Load more:
|
|
251
|
+
<button onClick={() => fetchNextPage()} disabled={!hasNextPage || isFetchingNextPage}>
|
|
252
|
+
{isFetchingNextPage ? 'Loading...' : hasNextPage ? 'Load More' : 'No more'}
|
|
253
|
+
</button>
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
**Gotcha:** `data` is `{ pages, pageParams }`, not flat data. `initialData` and `placeholderData` must match this shape.
|
|
257
|
+
|
|
258
|
+
## Paginated Queries (keep previous data)
|
|
259
|
+
|
|
260
|
+
```tsx
|
|
261
|
+
import { keepPreviousData, useQuery } from '@tanstack/react-query'
|
|
262
|
+
|
|
263
|
+
const { data, isPlaceholderData } = useQuery({
|
|
264
|
+
queryKey: ['projects', page],
|
|
265
|
+
queryFn: () => fetchProjects(page),
|
|
266
|
+
placeholderData: keepPreviousData,
|
|
267
|
+
})
|
|
268
|
+
|
|
269
|
+
// Prefetch next page for instant transitions:
|
|
270
|
+
queryClient.prefetchQuery({
|
|
271
|
+
queryKey: ['projects', page + 1],
|
|
272
|
+
queryFn: () => fetchProjects(page + 1),
|
|
273
|
+
})
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
## Prefetching
|
|
277
|
+
|
|
278
|
+
```tsx
|
|
279
|
+
// In event handlers (hover/focus):
|
|
280
|
+
const prefetch = () => queryClient.prefetchQuery(todosOptions())
|
|
281
|
+
<button onMouseEnter={prefetch} onFocus={prefetch} onClick={handleClick}>Show</button>
|
|
282
|
+
|
|
283
|
+
// In components (avoid Suspense waterfalls):
|
|
284
|
+
function Layout({ id }: { id: string }) {
|
|
285
|
+
usePrefetchQuery(commentsOptions(id)) // starts fetch immediately
|
|
286
|
+
return (
|
|
287
|
+
<Suspense fallback="Loading...">
|
|
288
|
+
<Article id={id} />
|
|
289
|
+
</Suspense>
|
|
290
|
+
)
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// Prefetch infinite queries:
|
|
294
|
+
queryClient.prefetchInfiniteQuery({
|
|
295
|
+
...projectsInfiniteOptions(),
|
|
296
|
+
pages: 3, // prefetch first 3 pages
|
|
297
|
+
})
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
## Dependent Queries
|
|
301
|
+
|
|
302
|
+
```tsx
|
|
303
|
+
const { data: user } = useQuery({
|
|
304
|
+
queryKey: ['user', email],
|
|
305
|
+
queryFn: () => getUserByEmail(email),
|
|
306
|
+
})
|
|
307
|
+
|
|
308
|
+
const { data: projects } = useQuery({
|
|
309
|
+
queryKey: ['projects', user?.id],
|
|
310
|
+
queryFn: () => getProjectsByUser(user!.id),
|
|
311
|
+
enabled: !!user?.id, // waits for user query
|
|
312
|
+
})
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
**Type-safe disabling with skipToken:**
|
|
316
|
+
|
|
317
|
+
```tsx
|
|
318
|
+
import { skipToken } from '@tanstack/react-query'
|
|
319
|
+
|
|
320
|
+
const { data } = useQuery({
|
|
321
|
+
queryKey: ['projects', userId],
|
|
322
|
+
queryFn: userId ? () => getProjects(userId) : skipToken,
|
|
323
|
+
})
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
`skipToken` prevents `refetch()` from working — use `enabled: false` if you need manual refetch.
|
|
327
|
+
|
|
328
|
+
## setQueryData — immutability
|
|
329
|
+
|
|
330
|
+
```tsx
|
|
331
|
+
// WRONG — mutating cache in place
|
|
332
|
+
queryClient.setQueryData(['todo', id], (old) => {
|
|
333
|
+
if (old) old.title = 'new' // DO NOT DO THIS
|
|
334
|
+
return old
|
|
335
|
+
})
|
|
336
|
+
|
|
337
|
+
// CORRECT — return new object
|
|
338
|
+
queryClient.setQueryData(['todo', id], (old) =>
|
|
339
|
+
old ? { ...old, title: 'new' } : old
|
|
340
|
+
)
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
## Further Reference
|
|
344
|
+
|
|
345
|
+
- **Full API signatures** (useQuery, useMutation, useInfiniteQuery, QueryClient): See [references/api-reference.md](references/api-reference.md)
|
|
346
|
+
- **SSR & Next.js** (hydration, App Router, streaming): See [references/ssr-nextjs.md](references/ssr-nextjs.md)
|
|
347
|
+
- **Testing** (renderHook, mocking, setup): See [references/testing.md](references/testing.md)
|
|
348
|
+
- **Advanced patterns** (TypeScript, Suspense, waterfalls, network modes): See [references/advanced-patterns.md](references/advanced-patterns.md)
|