@nextsparkjs/ai-workflow 0.1.0-beta.100
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/LICENSE +21 -0
- package/README.md +115 -0
- package/claude/_docs/workflows-optimizations.md +359 -0
- package/claude/agents/api-tester.md +634 -0
- package/claude/agents/architecture-supervisor.md +1351 -0
- package/claude/agents/backend-developer.md +997 -0
- package/claude/agents/backend-validator.md +417 -0
- package/claude/agents/bdd-docs-writer.md +737 -0
- package/claude/agents/block-developer.md +677 -0
- package/claude/agents/code-reviewer.md +1432 -0
- package/claude/agents/db-developer.md +721 -0
- package/claude/agents/db-validator.md +407 -0
- package/claude/agents/demo-video-generator.md +493 -0
- package/claude/agents/documentation-writer.md +1268 -0
- package/claude/agents/frontend-developer.md +1234 -0
- package/claude/agents/frontend-validator.md +777 -0
- package/claude/agents/functional-validator.md +630 -0
- package/claude/agents/mock-analyst.md +387 -0
- package/claude/agents/product-manager.md +963 -0
- package/claude/agents/qa-automation.md +1762 -0
- package/claude/agents/release-manager.md +634 -0
- package/claude/agents/selectors-translator.md +262 -0
- package/claude/agents/unit-test-writer.md +785 -0
- package/claude/agents/visual-comparator.md +329 -0
- package/claude/agents/workflow-maintainer.md +352 -0
- package/claude/commands/do/README.md +88 -0
- package/claude/commands/do/create-api.md +64 -0
- package/claude/commands/do/create-entity.md +66 -0
- package/claude/commands/do/create-migration.md +64 -0
- package/claude/commands/do/create-plugin.md +56 -0
- package/claude/commands/do/create-theme.md +70 -0
- package/claude/commands/do/mock-data.md +67 -0
- package/claude/commands/do/reset-db.md +71 -0
- package/claude/commands/do/setup-scheduled-action.md +75 -0
- package/claude/commands/do/sync-code-review.md +117 -0
- package/claude/commands/do/update-selectors.md +112 -0
- package/claude/commands/do/use-skills.md +90 -0
- package/claude/commands/do/validate-blocks.md +69 -0
- package/claude/commands/how-to/README.md +261 -0
- package/claude/commands/how-to/add-metadata.md +692 -0
- package/claude/commands/how-to/add-taxonomies.md +806 -0
- package/claude/commands/how-to/add-translations.md +571 -0
- package/claude/commands/how-to/create-api.md +577 -0
- package/claude/commands/how-to/create-block.md +575 -0
- package/claude/commands/how-to/create-child-entities.md +771 -0
- package/claude/commands/how-to/create-entity.md +597 -0
- package/claude/commands/how-to/create-migrations.md +605 -0
- package/claude/commands/how-to/create-plugin.md +654 -0
- package/claude/commands/how-to/customize-app.md +481 -0
- package/claude/commands/how-to/customize-dashboard.md +553 -0
- package/claude/commands/how-to/customize-theme.md +438 -0
- package/claude/commands/how-to/define-features-flows.md +632 -0
- package/claude/commands/how-to/deploy.md +507 -0
- package/claude/commands/how-to/handle-file-uploads.md +746 -0
- package/claude/commands/how-to/implement-search.md +1001 -0
- package/claude/commands/how-to/install-plugins.md +352 -0
- package/claude/commands/how-to/manage-test-coverage.md +984 -0
- package/claude/commands/how-to/run-tests.md +400 -0
- package/claude/commands/how-to/set-app-languages.md +601 -0
- package/claude/commands/how-to/set-plans-and-permissions.md +575 -0
- package/claude/commands/how-to/set-scheduled-actions.md +527 -0
- package/claude/commands/how-to/set-user-roles-and-permissions.md +550 -0
- package/claude/commands/how-to/setup-authentication.md +388 -0
- package/claude/commands/how-to/setup-claude-code.md +440 -0
- package/claude/commands/how-to/setup-database.md +274 -0
- package/claude/commands/how-to/setup-email-providers.md +598 -0
- package/claude/commands/how-to/setup-mobile-dev.md +627 -0
- package/claude/commands/how-to/start.md +500 -0
- package/claude/commands/how-to/use-devtools.md +639 -0
- package/claude/commands/how-to/use-superadmin.md +622 -0
- package/claude/commands/session/README.md +193 -0
- package/claude/commands/session/block-create.md +190 -0
- package/claude/commands/session/block-list.md +203 -0
- package/claude/commands/session/block-update.md +192 -0
- package/claude/commands/session/block-validate.md +218 -0
- package/claude/commands/session/changelog.md +115 -0
- package/claude/commands/session/close.md +225 -0
- package/claude/commands/session/commit.md +174 -0
- package/claude/commands/session/db-entity.md +206 -0
- package/claude/commands/session/db-fix.md +212 -0
- package/claude/commands/session/db-sample.md +206 -0
- package/claude/commands/session/demo.md +178 -0
- package/claude/commands/session/doc-bdd.md +207 -0
- package/claude/commands/session/doc-feature.md +218 -0
- package/claude/commands/session/doc-read.md +225 -0
- package/claude/commands/session/execute.md +204 -0
- package/claude/commands/session/explain.md +202 -0
- package/claude/commands/session/fix-bug.md +210 -0
- package/claude/commands/session/fix-build.md +182 -0
- package/claude/commands/session/fix-test.md +189 -0
- package/claude/commands/session/pending.md +232 -0
- package/claude/commands/session/refine.md +188 -0
- package/claude/commands/session/resume.md +192 -0
- package/claude/commands/session/review.md +192 -0
- package/claude/commands/session/scope-change.md +181 -0
- package/claude/commands/session/start-blocks.md +347 -0
- package/claude/commands/session/start.md +604 -0
- package/claude/commands/session/status.md +169 -0
- package/claude/commands/session/test-fix.md +221 -0
- package/claude/commands/session/test-run.md +203 -0
- package/claude/commands/session/test-write.md +242 -0
- package/claude/commands/session/validate.md +162 -0
- package/claude/config/context.json +40 -0
- package/claude/config/github.json +69 -0
- package/claude/config/github.schema.json +106 -0
- package/claude/config/team.json +46 -0
- package/claude/config/team.schema.json +106 -0
- package/claude/config/workspace.json +43 -0
- package/claude/config/workspace.schema.json +75 -0
- package/claude/skills/README.md +228 -0
- package/claude/skills/accessibility/SKILL.md +573 -0
- package/claude/skills/api-bypass-layers/SKILL.md +550 -0
- package/claude/skills/asana-integration/SKILL.md +499 -0
- package/claude/skills/better-auth/SKILL.md +666 -0
- package/claude/skills/billing-subscriptions/SKILL.md +660 -0
- package/claude/skills/block-decision-matrix/SKILL.md +359 -0
- package/claude/skills/clickup-integration/SKILL.md +434 -0
- package/claude/skills/core-theme-responsibilities/SKILL.md +485 -0
- package/claude/skills/create-plugin/SKILL.md +425 -0
- package/claude/skills/create-theme/SKILL.md +331 -0
- package/claude/skills/cypress-api/SKILL.md +511 -0
- package/claude/skills/cypress-api/scripts/generate-api-controller.py +329 -0
- package/claude/skills/cypress-api/scripts/generate-api-test.py +930 -0
- package/claude/skills/cypress-e2e/SKILL.md +526 -0
- package/claude/skills/cypress-e2e/scripts/extract-selectors.py +383 -0
- package/claude/skills/cypress-e2e/scripts/generate-uat-test.py +788 -0
- package/claude/skills/cypress-selectors/SKILL.md +309 -0
- package/claude/skills/cypress-selectors/scripts/extract-missing.py +243 -0
- package/claude/skills/cypress-selectors/scripts/generate-block-selectors.py +283 -0
- package/claude/skills/cypress-selectors/scripts/validate-selectors.py +145 -0
- package/claude/skills/database-migrations/SKILL.md +335 -0
- package/claude/skills/database-migrations/scripts/generate-sample-data.py +284 -0
- package/claude/skills/database-migrations/scripts/validate-migration.py +323 -0
- package/claude/skills/design-system/SKILL.md +682 -0
- package/claude/skills/documentation/SKILL.md +540 -0
- package/claude/skills/entity-api/SKILL.md +482 -0
- package/claude/skills/entity-system/SKILL.md +635 -0
- package/claude/skills/entity-system/scripts/generate-child-migration.py +298 -0
- package/claude/skills/entity-system/scripts/generate-metas-migration.py +233 -0
- package/claude/skills/entity-system/scripts/generate-migration.py +382 -0
- package/claude/skills/entity-system/scripts/generate-sample-data.py +418 -0
- package/claude/skills/entity-system/scripts/scaffold-entity.py +661 -0
- package/claude/skills/github/SKILL.md +467 -0
- package/claude/skills/i18n-nextintl/SKILL.md +302 -0
- package/claude/skills/i18n-nextintl/scripts/add-translation.py +243 -0
- package/claude/skills/i18n-nextintl/scripts/extract-hardcoded.py +246 -0
- package/claude/skills/i18n-nextintl/scripts/validate-translations.py +260 -0
- package/claude/skills/impact-analysis/SKILL.md +203 -0
- package/claude/skills/jest-unit/SKILL.md +306 -0
- package/claude/skills/jest-unit/references/component-testing.md +371 -0
- package/claude/skills/jest-unit/references/mocking-patterns.md +380 -0
- package/claude/skills/jest-unit/references/service-hook-testing.md +454 -0
- package/claude/skills/jira-integration/SKILL.md +539 -0
- package/claude/skills/media-library/SKILL.md +743 -0
- package/claude/skills/mock-analysis/SKILL.md +276 -0
- package/claude/skills/monorepo-architecture/SKILL.md +162 -0
- package/claude/skills/nextjs-api-development/SKILL.md +364 -0
- package/claude/skills/nextjs-api-development/scripts/generate-crud-tests.py +456 -0
- package/claude/skills/nextjs-api-development/scripts/scaffold-endpoint.py +481 -0
- package/claude/skills/nextjs-api-development/scripts/validate-api.py +283 -0
- package/claude/skills/notion-integration/SKILL.md +641 -0
- package/claude/skills/npm-development-workflow/SKILL.md +480 -0
- package/claude/skills/page-builder-blocks/SKILL.md +530 -0
- package/claude/skills/page-builder-blocks/scripts/scaffold-block.py +444 -0
- package/claude/skills/permissions-system/SKILL.md +619 -0
- package/claude/skills/plugins/SKILL.md +340 -0
- package/claude/skills/plugins/references/plugin-templates.md +414 -0
- package/claude/skills/plugins/references/plugin-testing.md +353 -0
- package/claude/skills/plugins/references/plugin-types.md +198 -0
- package/claude/skills/plugins/scripts/scaffold-plugin.py +443 -0
- package/claude/skills/pom-patterns/SKILL.md +452 -0
- package/claude/skills/pom-patterns/scripts/generate-pom.py +392 -0
- package/claude/skills/rate-limiting/SKILL.md +342 -0
- package/claude/skills/react-best-practices/AGENTS.md +2410 -0
- package/claude/skills/react-best-practices/README.md +123 -0
- package/claude/skills/react-best-practices/SKILL.md +125 -0
- package/claude/skills/react-best-practices/metadata.json +15 -0
- package/claude/skills/react-best-practices/rules/_sections.md +46 -0
- package/claude/skills/react-best-practices/rules/_template.md +28 -0
- package/claude/skills/react-best-practices/rules/advanced-event-handler-refs.md +55 -0
- package/claude/skills/react-best-practices/rules/advanced-use-latest.md +49 -0
- package/claude/skills/react-best-practices/rules/async-api-routes.md +38 -0
- package/claude/skills/react-best-practices/rules/async-defer-await.md +80 -0
- package/claude/skills/react-best-practices/rules/async-dependencies.md +36 -0
- package/claude/skills/react-best-practices/rules/async-parallel.md +28 -0
- package/claude/skills/react-best-practices/rules/async-suspense-boundaries.md +99 -0
- package/claude/skills/react-best-practices/rules/bundle-barrel-imports.md +59 -0
- package/claude/skills/react-best-practices/rules/bundle-conditional.md +31 -0
- package/claude/skills/react-best-practices/rules/bundle-defer-third-party.md +49 -0
- package/claude/skills/react-best-practices/rules/bundle-dynamic-imports.md +35 -0
- package/claude/skills/react-best-practices/rules/bundle-preload.md +50 -0
- package/claude/skills/react-best-practices/rules/client-event-listeners.md +74 -0
- package/claude/skills/react-best-practices/rules/client-localstorage-schema.md +71 -0
- package/claude/skills/react-best-practices/rules/client-passive-event-listeners.md +48 -0
- package/claude/skills/react-best-practices/rules/client-swr-dedup.md +56 -0
- package/claude/skills/react-best-practices/rules/js-batch-dom-css.md +82 -0
- package/claude/skills/react-best-practices/rules/js-cache-function-results.md +80 -0
- package/claude/skills/react-best-practices/rules/js-cache-property-access.md +28 -0
- package/claude/skills/react-best-practices/rules/js-cache-storage.md +70 -0
- package/claude/skills/react-best-practices/rules/js-combine-iterations.md +32 -0
- package/claude/skills/react-best-practices/rules/js-early-exit.md +50 -0
- package/claude/skills/react-best-practices/rules/js-hoist-regexp.md +45 -0
- package/claude/skills/react-best-practices/rules/js-index-maps.md +37 -0
- package/claude/skills/react-best-practices/rules/js-length-check-first.md +49 -0
- package/claude/skills/react-best-practices/rules/js-min-max-loop.md +82 -0
- package/claude/skills/react-best-practices/rules/js-set-map-lookups.md +24 -0
- package/claude/skills/react-best-practices/rules/js-tosorted-immutable.md +57 -0
- package/claude/skills/react-best-practices/rules/rendering-activity.md +26 -0
- package/claude/skills/react-best-practices/rules/rendering-animate-svg-wrapper.md +47 -0
- package/claude/skills/react-best-practices/rules/rendering-conditional-render.md +40 -0
- package/claude/skills/react-best-practices/rules/rendering-content-visibility.md +38 -0
- package/claude/skills/react-best-practices/rules/rendering-hoist-jsx.md +46 -0
- package/claude/skills/react-best-practices/rules/rendering-hydration-no-flicker.md +82 -0
- package/claude/skills/react-best-practices/rules/rendering-svg-precision.md +28 -0
- package/claude/skills/react-best-practices/rules/rerender-defer-reads.md +39 -0
- package/claude/skills/react-best-practices/rules/rerender-dependencies.md +45 -0
- package/claude/skills/react-best-practices/rules/rerender-derived-state.md +29 -0
- package/claude/skills/react-best-practices/rules/rerender-functional-setstate.md +74 -0
- package/claude/skills/react-best-practices/rules/rerender-lazy-state-init.md +58 -0
- package/claude/skills/react-best-practices/rules/rerender-memo.md +44 -0
- package/claude/skills/react-best-practices/rules/rerender-transitions.md +40 -0
- package/claude/skills/react-best-practices/rules/server-after-nonblocking.md +73 -0
- package/claude/skills/react-best-practices/rules/server-cache-lru.md +41 -0
- package/claude/skills/react-best-practices/rules/server-cache-react.md +76 -0
- package/claude/skills/react-best-practices/rules/server-parallel-fetching.md +83 -0
- package/claude/skills/react-best-practices/rules/server-serialization.md +38 -0
- package/claude/skills/react-patterns/SKILL.md +688 -0
- package/claude/skills/registry-system/SKILL.md +331 -0
- package/claude/skills/scheduled-actions/SKILL.md +671 -0
- package/claude/skills/scope-enforcement/SKILL.md +542 -0
- package/claude/skills/scope-enforcement/scripts/validate-scope.py +357 -0
- package/claude/skills/server-actions/SKILL.md +493 -0
- package/claude/skills/service-layer/SKILL.md +587 -0
- package/claude/skills/session-management/SKILL.md +266 -0
- package/claude/skills/session-management/scripts/create-session.py +166 -0
- package/claude/skills/session-management/scripts/iteration-close.sh +105 -0
- package/claude/skills/session-management/scripts/iteration-init.sh +180 -0
- package/claude/skills/session-management/scripts/session-archive.sh +87 -0
- package/claude/skills/session-management/scripts/session-close.sh +133 -0
- package/claude/skills/session-management/scripts/session-init.sh +225 -0
- package/claude/skills/session-management/scripts/session-list.sh +163 -0
- package/claude/skills/session-management/scripts/split-plan.sh +116 -0
- package/claude/skills/shadcn-components/SKILL.md +586 -0
- package/claude/skills/shadcn-theming/SKILL.md +446 -0
- package/claude/skills/suspense-loading/SKILL.md +280 -0
- package/claude/skills/tailwind-theming/SKILL.md +507 -0
- package/claude/skills/tanstack-query/SKILL.md +608 -0
- package/claude/skills/test-coverage/SKILL.md +239 -0
- package/claude/skills/web-design-guidelines/SKILL.md +39 -0
- package/claude/skills/zod-validation/SKILL.md +537 -0
- package/claude/templates/blocks/progress.md +86 -0
- package/claude/templates/iteration/changes.md +61 -0
- package/claude/templates/iteration/progress.md +55 -0
- package/claude/templates/log.md +31 -0
- package/claude/templates/story/context.md +77 -0
- package/claude/templates/story/pendings.md +37 -0
- package/claude/templates/story/plan.md +299 -0
- package/claude/templates/story/requirements.md +109 -0
- package/claude/templates/story/scope.json +10 -0
- package/claude/templates/story/tests.md +91 -0
- package/claude/templates/task/progress.md +58 -0
- package/claude/templates/task/requirements.md +54 -0
- package/claude/workflows/README.md +154 -0
- package/claude/workflows/blocks.md +614 -0
- package/claude/workflows/story.md +1207 -0
- package/claude/workflows/task.md +927 -0
- package/claude/workflows/tweak.md +527 -0
- package/cursor/.gitkeep +0 -0
- package/package.json +35 -0
- package/scripts/postinstall.mjs +198 -0
- package/scripts/setup.mjs +282 -0
- package/scripts/sync.mjs +209 -0
|
@@ -0,0 +1,608 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: tanstack-query
|
|
3
|
+
description: |
|
|
4
|
+
TanStack Query (React Query) patterns for data fetching in this Next.js application.
|
|
5
|
+
Covers useQuery, useMutation, optimistic updates, cache invalidation, and anti-patterns.
|
|
6
|
+
Use this skill when implementing data fetching or state management with server data.
|
|
7
|
+
allowed-tools: Read, Glob, Grep
|
|
8
|
+
version: 1.0.0
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
# TanStack Query Skill
|
|
12
|
+
|
|
13
|
+
Data fetching patterns and best practices with TanStack Query (React Query).
|
|
14
|
+
|
|
15
|
+
## Architecture Overview
|
|
16
|
+
|
|
17
|
+
```
|
|
18
|
+
core/providers/
|
|
19
|
+
└── query-provider.tsx # QueryClient configuration
|
|
20
|
+
|
|
21
|
+
core/hooks/
|
|
22
|
+
├── useEntityQuery.ts # Generic entity query hook
|
|
23
|
+
├── useEntityMutations.ts # CRUD mutations with optimistic updates
|
|
24
|
+
└── useUserProfile.ts # Example simple mutation
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## When to Use This Skill
|
|
28
|
+
|
|
29
|
+
- Fetching data from API endpoints
|
|
30
|
+
- Implementing CRUD operations
|
|
31
|
+
- Managing server state and caching
|
|
32
|
+
- Implementing optimistic updates
|
|
33
|
+
- Avoiding useEffect anti-patterns
|
|
34
|
+
|
|
35
|
+
## Query Client Setup
|
|
36
|
+
|
|
37
|
+
```typescript
|
|
38
|
+
// core/providers/query-provider.tsx
|
|
39
|
+
export function QueryProvider({ children }: { children: React.ReactNode }) {
|
|
40
|
+
const [queryClient] = useState(
|
|
41
|
+
() =>
|
|
42
|
+
new QueryClient({
|
|
43
|
+
defaultOptions: {
|
|
44
|
+
queries: {
|
|
45
|
+
staleTime: 60 * 1000, // 1 minute default
|
|
46
|
+
refetchOnWindowFocus: false, // Disabled
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
})
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
return (
|
|
53
|
+
<QueryClientProvider client={queryClient}>
|
|
54
|
+
{children}
|
|
55
|
+
{process.env.NEXT_PUBLIC_RQ_DEVTOOLS === 'true' && (
|
|
56
|
+
<ReactQueryDevtools initialIsOpen={false} />
|
|
57
|
+
)}
|
|
58
|
+
</QueryClientProvider>
|
|
59
|
+
)
|
|
60
|
+
}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
**Key Configuration:**
|
|
64
|
+
|
|
65
|
+
| Setting | Value | Reason |
|
|
66
|
+
|---------|-------|--------|
|
|
67
|
+
| `staleTime` | 60 seconds | Prevents excessive refetching |
|
|
68
|
+
| `refetchOnWindowFocus` | false | Manual control over refetching |
|
|
69
|
+
| `gcTime` | 5 minutes (default) | Cache cleanup |
|
|
70
|
+
|
|
71
|
+
## Query Key Conventions
|
|
72
|
+
|
|
73
|
+
Query keys should be hierarchical arrays for proper cache management:
|
|
74
|
+
|
|
75
|
+
```typescript
|
|
76
|
+
// Pattern: ['domain', 'resource', filters, id]
|
|
77
|
+
|
|
78
|
+
// Entity list with filters
|
|
79
|
+
['entity', 'tasks', { page: 1, search: 'test', status: 'active' }]
|
|
80
|
+
|
|
81
|
+
// Single entity
|
|
82
|
+
['entity', 'tasks', 'task-123']
|
|
83
|
+
|
|
84
|
+
// Single entity with options
|
|
85
|
+
['entity', 'tasks', 'task-123', { includeChildren: true }]
|
|
86
|
+
|
|
87
|
+
// User-specific data
|
|
88
|
+
['user-profile']
|
|
89
|
+
['user-settings', 'notifications']
|
|
90
|
+
|
|
91
|
+
// Admin data
|
|
92
|
+
['superadmin-users', search, roleFilter, statusFilter, page]
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## useQuery Patterns
|
|
96
|
+
|
|
97
|
+
### Basic Query
|
|
98
|
+
|
|
99
|
+
```typescript
|
|
100
|
+
import { useQuery } from '@tanstack/react-query'
|
|
101
|
+
|
|
102
|
+
function useTaskList(filters: TaskFilters) {
|
|
103
|
+
return useQuery({
|
|
104
|
+
queryKey: ['entity', 'tasks', filters],
|
|
105
|
+
queryFn: async () => {
|
|
106
|
+
const params = new URLSearchParams({
|
|
107
|
+
page: String(filters.page),
|
|
108
|
+
limit: String(filters.limit),
|
|
109
|
+
...(filters.status && { status: filters.status }),
|
|
110
|
+
...(filters.search && { search: filters.search }),
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
const response = await fetch(`/api/v1/tasks?${params}`)
|
|
114
|
+
if (!response.ok) {
|
|
115
|
+
throw new Error('Failed to fetch tasks')
|
|
116
|
+
}
|
|
117
|
+
return response.json()
|
|
118
|
+
},
|
|
119
|
+
})
|
|
120
|
+
}
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### Query with Conditional Enabling
|
|
124
|
+
|
|
125
|
+
```typescript
|
|
126
|
+
function useTask(id: string | null) {
|
|
127
|
+
return useQuery({
|
|
128
|
+
queryKey: ['entity', 'tasks', id],
|
|
129
|
+
queryFn: async () => {
|
|
130
|
+
const response = await fetch(`/api/v1/tasks/${id}`)
|
|
131
|
+
if (!response.ok) throw new Error('Failed to fetch task')
|
|
132
|
+
return response.json()
|
|
133
|
+
},
|
|
134
|
+
enabled: !!id, // Only fetch when id exists
|
|
135
|
+
})
|
|
136
|
+
}
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
### Query with Authentication Guard
|
|
140
|
+
|
|
141
|
+
```typescript
|
|
142
|
+
function useEntityQuery(entityConfig: EntityConfig, filters: Filters) {
|
|
143
|
+
const { user } = useAuth()
|
|
144
|
+
|
|
145
|
+
return useQuery({
|
|
146
|
+
queryKey: ['entity', entityConfig.slug, filters],
|
|
147
|
+
queryFn: async () => {
|
|
148
|
+
const response = await fetch(`/api/v1/${entityConfig.slug}?${params}`)
|
|
149
|
+
if (!response.ok) throw new Error('Failed to fetch')
|
|
150
|
+
return response.json()
|
|
151
|
+
},
|
|
152
|
+
enabled: !!user, // Only fetch when authenticated
|
|
153
|
+
staleTime: 1000 * 60 * 5, // 5 minutes for entity lists
|
|
154
|
+
gcTime: 1000 * 60 * 60, // 1 hour garbage collection
|
|
155
|
+
})
|
|
156
|
+
}
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
### Query Options Summary
|
|
160
|
+
|
|
161
|
+
| Option | Type | Description |
|
|
162
|
+
|--------|------|-------------|
|
|
163
|
+
| `queryKey` | `unknown[]` | Cache key (required) |
|
|
164
|
+
| `queryFn` | `() => Promise<T>` | Fetch function (required) |
|
|
165
|
+
| `enabled` | `boolean` | Conditional fetching |
|
|
166
|
+
| `staleTime` | `number` | Time before data is stale (ms) |
|
|
167
|
+
| `gcTime` | `number` | Cache retention time (ms) |
|
|
168
|
+
| `retry` | `number \| boolean` | Retry attempts |
|
|
169
|
+
| `refetchOnWindowFocus` | `boolean` | Refetch on tab focus |
|
|
170
|
+
| `refetchInterval` | `number` | Polling interval (ms) |
|
|
171
|
+
|
|
172
|
+
## useMutation Patterns
|
|
173
|
+
|
|
174
|
+
### Simple Mutation
|
|
175
|
+
|
|
176
|
+
```typescript
|
|
177
|
+
import { useMutation, useQueryClient } from '@tanstack/react-query'
|
|
178
|
+
|
|
179
|
+
function useCreateTask() {
|
|
180
|
+
const queryClient = useQueryClient()
|
|
181
|
+
|
|
182
|
+
return useMutation({
|
|
183
|
+
mutationFn: async (data: CreateTaskData) => {
|
|
184
|
+
const response = await fetch('/api/v1/tasks', {
|
|
185
|
+
method: 'POST',
|
|
186
|
+
headers: { 'Content-Type': 'application/json' },
|
|
187
|
+
body: JSON.stringify(data),
|
|
188
|
+
})
|
|
189
|
+
if (!response.ok) throw new Error('Failed to create task')
|
|
190
|
+
return response.json()
|
|
191
|
+
},
|
|
192
|
+
onSuccess: () => {
|
|
193
|
+
// Invalidate all task queries to refetch
|
|
194
|
+
queryClient.invalidateQueries({ queryKey: ['entity', 'tasks'] })
|
|
195
|
+
},
|
|
196
|
+
})
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// Usage
|
|
200
|
+
function CreateTaskForm() {
|
|
201
|
+
const createTask = useCreateTask()
|
|
202
|
+
|
|
203
|
+
const handleSubmit = async (data: CreateTaskData) => {
|
|
204
|
+
try {
|
|
205
|
+
await createTask.mutateAsync(data)
|
|
206
|
+
toast.success('Task created!')
|
|
207
|
+
} catch (error) {
|
|
208
|
+
toast.error('Failed to create task')
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
return (
|
|
213
|
+
<form onSubmit={handleSubmit}>
|
|
214
|
+
{/* form fields */}
|
|
215
|
+
<Button disabled={createTask.isPending}>
|
|
216
|
+
{createTask.isPending ? 'Creating...' : 'Create'}
|
|
217
|
+
</Button>
|
|
218
|
+
</form>
|
|
219
|
+
)
|
|
220
|
+
}
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
### Mutation with Optimistic Updates
|
|
224
|
+
|
|
225
|
+
```typescript
|
|
226
|
+
function useEntityMutations(entityConfig: EntityConfig) {
|
|
227
|
+
const queryClient = useQueryClient()
|
|
228
|
+
const baseQueryKey = ['entity', entityConfig.slug]
|
|
229
|
+
|
|
230
|
+
const createMutation = useMutation({
|
|
231
|
+
mutationFn: async (data: Record<string, unknown>) => {
|
|
232
|
+
const response = await fetch(`/api/v1/${entityConfig.slug}`, {
|
|
233
|
+
method: 'POST',
|
|
234
|
+
headers: { 'Content-Type': 'application/json' },
|
|
235
|
+
body: JSON.stringify(data),
|
|
236
|
+
})
|
|
237
|
+
if (!response.ok) throw new Error('Failed to create')
|
|
238
|
+
return response.json()
|
|
239
|
+
},
|
|
240
|
+
|
|
241
|
+
// OPTIMISTIC UPDATE
|
|
242
|
+
onMutate: async (newItem) => {
|
|
243
|
+
// 1. Cancel outgoing refetches to avoid overwriting optimistic update
|
|
244
|
+
await queryClient.cancelQueries({ queryKey: baseQueryKey })
|
|
245
|
+
|
|
246
|
+
// 2. Snapshot current data for rollback
|
|
247
|
+
const previousData = queryClient.getQueriesData({ queryKey: baseQueryKey })
|
|
248
|
+
|
|
249
|
+
// 3. Optimistically update all matching queries
|
|
250
|
+
queryClient.setQueriesData({ queryKey: baseQueryKey }, (old: any) => {
|
|
251
|
+
if (!old?.items) return old
|
|
252
|
+
return {
|
|
253
|
+
...old,
|
|
254
|
+
items: [
|
|
255
|
+
{ ...newItem, id: `temp-${Date.now()}` }, // Temporary ID
|
|
256
|
+
...old.items
|
|
257
|
+
],
|
|
258
|
+
total: old.total + 1,
|
|
259
|
+
}
|
|
260
|
+
})
|
|
261
|
+
|
|
262
|
+
// 4. Return context for rollback
|
|
263
|
+
return { previousData }
|
|
264
|
+
},
|
|
265
|
+
|
|
266
|
+
// ROLLBACK ON ERROR
|
|
267
|
+
onError: (error, variables, context) => {
|
|
268
|
+
if (context?.previousData) {
|
|
269
|
+
context.previousData.forEach(([queryKey, data]) => {
|
|
270
|
+
queryClient.setQueryData(queryKey, data)
|
|
271
|
+
})
|
|
272
|
+
}
|
|
273
|
+
toast.error('Failed to create item')
|
|
274
|
+
},
|
|
275
|
+
|
|
276
|
+
// SYNC WITH SERVER
|
|
277
|
+
onSettled: () => {
|
|
278
|
+
// Always refetch after mutation to sync with server
|
|
279
|
+
queryClient.invalidateQueries({ queryKey: baseQueryKey })
|
|
280
|
+
},
|
|
281
|
+
})
|
|
282
|
+
|
|
283
|
+
const updateMutation = useMutation({
|
|
284
|
+
mutationFn: async ({ id, data }: { id: string; data: Record<string, unknown> }) => {
|
|
285
|
+
const response = await fetch(`/api/v1/${entityConfig.slug}/${id}`, {
|
|
286
|
+
method: 'PATCH',
|
|
287
|
+
headers: { 'Content-Type': 'application/json' },
|
|
288
|
+
body: JSON.stringify(data),
|
|
289
|
+
})
|
|
290
|
+
if (!response.ok) throw new Error('Failed to update')
|
|
291
|
+
return response.json()
|
|
292
|
+
},
|
|
293
|
+
|
|
294
|
+
onMutate: async ({ id, data }) => {
|
|
295
|
+
await queryClient.cancelQueries({ queryKey: baseQueryKey })
|
|
296
|
+
const previousData = queryClient.getQueriesData({ queryKey: baseQueryKey })
|
|
297
|
+
|
|
298
|
+
// Update item in all matching queries
|
|
299
|
+
queryClient.setQueriesData({ queryKey: baseQueryKey }, (old: any) => {
|
|
300
|
+
if (!old?.items) return old
|
|
301
|
+
return {
|
|
302
|
+
...old,
|
|
303
|
+
items: old.items.map((item: any) =>
|
|
304
|
+
item.id === id ? { ...item, ...data } : item
|
|
305
|
+
),
|
|
306
|
+
}
|
|
307
|
+
})
|
|
308
|
+
|
|
309
|
+
return { previousData }
|
|
310
|
+
},
|
|
311
|
+
|
|
312
|
+
onError: (error, variables, context) => {
|
|
313
|
+
if (context?.previousData) {
|
|
314
|
+
context.previousData.forEach(([queryKey, data]) => {
|
|
315
|
+
queryClient.setQueryData(queryKey, data)
|
|
316
|
+
})
|
|
317
|
+
}
|
|
318
|
+
},
|
|
319
|
+
|
|
320
|
+
onSettled: () => {
|
|
321
|
+
queryClient.invalidateQueries({ queryKey: baseQueryKey })
|
|
322
|
+
},
|
|
323
|
+
})
|
|
324
|
+
|
|
325
|
+
const deleteMutation = useMutation({
|
|
326
|
+
mutationFn: async (id: string) => {
|
|
327
|
+
const response = await fetch(`/api/v1/${entityConfig.slug}/${id}`, {
|
|
328
|
+
method: 'DELETE',
|
|
329
|
+
})
|
|
330
|
+
if (!response.ok) throw new Error('Failed to delete')
|
|
331
|
+
return response.json()
|
|
332
|
+
},
|
|
333
|
+
|
|
334
|
+
onMutate: async (id) => {
|
|
335
|
+
await queryClient.cancelQueries({ queryKey: baseQueryKey })
|
|
336
|
+
const previousData = queryClient.getQueriesData({ queryKey: baseQueryKey })
|
|
337
|
+
|
|
338
|
+
// Remove item from all matching queries
|
|
339
|
+
queryClient.setQueriesData({ queryKey: baseQueryKey }, (old: any) => {
|
|
340
|
+
if (!old?.items) return old
|
|
341
|
+
return {
|
|
342
|
+
...old,
|
|
343
|
+
items: old.items.filter((item: any) => item.id !== id),
|
|
344
|
+
total: old.total - 1,
|
|
345
|
+
}
|
|
346
|
+
})
|
|
347
|
+
|
|
348
|
+
return { previousData }
|
|
349
|
+
},
|
|
350
|
+
|
|
351
|
+
onError: (error, variables, context) => {
|
|
352
|
+
if (context?.previousData) {
|
|
353
|
+
context.previousData.forEach(([queryKey, data]) => {
|
|
354
|
+
queryClient.setQueryData(queryKey, data)
|
|
355
|
+
})
|
|
356
|
+
}
|
|
357
|
+
},
|
|
358
|
+
|
|
359
|
+
onSettled: () => {
|
|
360
|
+
queryClient.invalidateQueries({ queryKey: baseQueryKey })
|
|
361
|
+
},
|
|
362
|
+
})
|
|
363
|
+
|
|
364
|
+
return {
|
|
365
|
+
create: createMutation,
|
|
366
|
+
update: updateMutation,
|
|
367
|
+
delete: deleteMutation,
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
## Cache Invalidation Strategies
|
|
373
|
+
|
|
374
|
+
### Broad Invalidation
|
|
375
|
+
|
|
376
|
+
Affects all queries with matching prefix:
|
|
377
|
+
|
|
378
|
+
```typescript
|
|
379
|
+
// Invalidate all task queries (any filters)
|
|
380
|
+
queryClient.invalidateQueries({ queryKey: ['entity', 'tasks'] })
|
|
381
|
+
|
|
382
|
+
// Invalidate all entity queries
|
|
383
|
+
queryClient.invalidateQueries({ queryKey: ['entity'] })
|
|
384
|
+
```
|
|
385
|
+
|
|
386
|
+
### Specific Invalidation
|
|
387
|
+
|
|
388
|
+
Target specific queries:
|
|
389
|
+
|
|
390
|
+
```typescript
|
|
391
|
+
// Invalidate single task
|
|
392
|
+
queryClient.invalidateQueries({
|
|
393
|
+
queryKey: ['entity', 'tasks', 'task-123']
|
|
394
|
+
})
|
|
395
|
+
|
|
396
|
+
// Invalidate list with specific filters
|
|
397
|
+
queryClient.invalidateQueries({
|
|
398
|
+
queryKey: ['entity', 'tasks', { status: 'active' }],
|
|
399
|
+
exact: true // Only exact match
|
|
400
|
+
})
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
### Direct Cache Update
|
|
404
|
+
|
|
405
|
+
Update without refetch:
|
|
406
|
+
|
|
407
|
+
```typescript
|
|
408
|
+
// Update single item in cache
|
|
409
|
+
queryClient.setQueryData(
|
|
410
|
+
['entity', 'tasks', 'task-123'],
|
|
411
|
+
(old) => ({ ...old, status: 'completed' })
|
|
412
|
+
)
|
|
413
|
+
|
|
414
|
+
// Update all matching queries
|
|
415
|
+
queryClient.setQueriesData(
|
|
416
|
+
{ queryKey: ['entity', 'tasks'] },
|
|
417
|
+
(old: any) => ({
|
|
418
|
+
...old,
|
|
419
|
+
items: old.items.map((item: any) =>
|
|
420
|
+
item.id === 'task-123' ? { ...item, status: 'completed' } : item
|
|
421
|
+
),
|
|
422
|
+
})
|
|
423
|
+
)
|
|
424
|
+
```
|
|
425
|
+
|
|
426
|
+
## State Management Hierarchy
|
|
427
|
+
|
|
428
|
+
```
|
|
429
|
+
1. Server State → TanStack Query (useQuery, useMutation)
|
|
430
|
+
2. URL State → Search params (shareable, bookmarkable)
|
|
431
|
+
3. Component State → useState (local, ephemeral)
|
|
432
|
+
4. Context API → Cross-component (theme, auth, user)
|
|
433
|
+
5. External Stores → useSyncExternalStore (third-party)
|
|
434
|
+
```
|
|
435
|
+
|
|
436
|
+
**Rule:** Use TanStack Query for ALL server data. Don't store server data in useState.
|
|
437
|
+
|
|
438
|
+
## Loading & Error States
|
|
439
|
+
|
|
440
|
+
```typescript
|
|
441
|
+
function TaskList() {
|
|
442
|
+
const { data, isLoading, isError, error, refetch } = useTaskList(filters)
|
|
443
|
+
|
|
444
|
+
if (isLoading) {
|
|
445
|
+
return <Skeleton count={5} />
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
if (isError) {
|
|
449
|
+
return (
|
|
450
|
+
<Alert variant="destructive">
|
|
451
|
+
<AlertTitle>Error</AlertTitle>
|
|
452
|
+
<AlertDescription>
|
|
453
|
+
{error.message}
|
|
454
|
+
<Button onClick={() => refetch()}>Retry</Button>
|
|
455
|
+
</AlertDescription>
|
|
456
|
+
</Alert>
|
|
457
|
+
)
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
return (
|
|
461
|
+
<ul>
|
|
462
|
+
{data.items.map((task) => (
|
|
463
|
+
<TaskItem key={task.id} task={task} />
|
|
464
|
+
))}
|
|
465
|
+
</ul>
|
|
466
|
+
)
|
|
467
|
+
}
|
|
468
|
+
```
|
|
469
|
+
|
|
470
|
+
### State Properties
|
|
471
|
+
|
|
472
|
+
| Property | Description |
|
|
473
|
+
|----------|-------------|
|
|
474
|
+
| `isLoading` | First fetch, no data yet |
|
|
475
|
+
| `isFetching` | Any fetch (including background) |
|
|
476
|
+
| `isPending` | Mutation in progress |
|
|
477
|
+
| `isError` | Query/mutation failed |
|
|
478
|
+
| `isSuccess` | Query/mutation succeeded |
|
|
479
|
+
| `data` | Query result |
|
|
480
|
+
| `error` | Error object |
|
|
481
|
+
|
|
482
|
+
## Anti-Patterns (CRITICAL)
|
|
483
|
+
|
|
484
|
+
### FORBIDDEN: useEffect for Data Fetching
|
|
485
|
+
|
|
486
|
+
```typescript
|
|
487
|
+
// ❌ NEVER DO THIS
|
|
488
|
+
function TaskList() {
|
|
489
|
+
const [tasks, setTasks] = useState([])
|
|
490
|
+
const [loading, setLoading] = useState(true)
|
|
491
|
+
|
|
492
|
+
useEffect(() => {
|
|
493
|
+
setLoading(true)
|
|
494
|
+
fetch('/api/v1/tasks')
|
|
495
|
+
.then(res => res.json())
|
|
496
|
+
.then(data => {
|
|
497
|
+
setTasks(data.items)
|
|
498
|
+
setLoading(false)
|
|
499
|
+
})
|
|
500
|
+
}, [])
|
|
501
|
+
|
|
502
|
+
// Problems: No caching, no error handling, no refetch, race conditions
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
// ✅ CORRECT
|
|
506
|
+
function TaskList() {
|
|
507
|
+
const { data, isLoading, error } = useQuery({
|
|
508
|
+
queryKey: ['entity', 'tasks'],
|
|
509
|
+
queryFn: () => fetch('/api/v1/tasks').then(res => res.json())
|
|
510
|
+
})
|
|
511
|
+
|
|
512
|
+
// Benefits: Caching, error handling, automatic refetch, deduplication
|
|
513
|
+
}
|
|
514
|
+
```
|
|
515
|
+
|
|
516
|
+
### FORBIDDEN: useEffect for Derived State
|
|
517
|
+
|
|
518
|
+
```typescript
|
|
519
|
+
// ❌ NEVER DO THIS
|
|
520
|
+
function TaskStats({ tasks }) {
|
|
521
|
+
const [completedCount, setCompletedCount] = useState(0)
|
|
522
|
+
|
|
523
|
+
useEffect(() => {
|
|
524
|
+
setCompletedCount(tasks.filter(t => t.status === 'completed').length)
|
|
525
|
+
}, [tasks])
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
// ✅ CORRECT - Calculate during render
|
|
529
|
+
function TaskStats({ tasks }) {
|
|
530
|
+
const completedCount = useMemo(
|
|
531
|
+
() => tasks.filter(t => t.status === 'completed').length,
|
|
532
|
+
[tasks]
|
|
533
|
+
)
|
|
534
|
+
}
|
|
535
|
+
```
|
|
536
|
+
|
|
537
|
+
### FORBIDDEN: Storing Server Data in State
|
|
538
|
+
|
|
539
|
+
```typescript
|
|
540
|
+
// ❌ NEVER DO THIS
|
|
541
|
+
function TaskPage() {
|
|
542
|
+
const { data } = useTaskList()
|
|
543
|
+
const [tasks, setTasks] = useState([])
|
|
544
|
+
|
|
545
|
+
useEffect(() => {
|
|
546
|
+
if (data) setTasks(data.items)
|
|
547
|
+
}, [data])
|
|
548
|
+
|
|
549
|
+
// Now have TWO sources of truth!
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
// ✅ CORRECT - Use query data directly
|
|
553
|
+
function TaskPage() {
|
|
554
|
+
const { data } = useTaskList()
|
|
555
|
+
const tasks = data?.items ?? []
|
|
556
|
+
|
|
557
|
+
// Single source of truth
|
|
558
|
+
}
|
|
559
|
+
```
|
|
560
|
+
|
|
561
|
+
### FORBIDDEN: Missing Query Keys for Filters
|
|
562
|
+
|
|
563
|
+
```typescript
|
|
564
|
+
// ❌ WRONG - Same key regardless of filters
|
|
565
|
+
useQuery({
|
|
566
|
+
queryKey: ['tasks'],
|
|
567
|
+
queryFn: () => fetch(`/api/v1/tasks?status=${status}`)
|
|
568
|
+
})
|
|
569
|
+
|
|
570
|
+
// ✅ CORRECT - Include filters in key
|
|
571
|
+
useQuery({
|
|
572
|
+
queryKey: ['tasks', { status }],
|
|
573
|
+
queryFn: () => fetch(`/api/v1/tasks?status=${status}`)
|
|
574
|
+
})
|
|
575
|
+
```
|
|
576
|
+
|
|
577
|
+
## Key Conventions
|
|
578
|
+
|
|
579
|
+
| Aspect | Convention |
|
|
580
|
+
|--------|-----------|
|
|
581
|
+
| **Query Keys** | `['domain', 'resource', filters, id]` |
|
|
582
|
+
| **Stale Time** | 60s (global), 5min (entity lists) |
|
|
583
|
+
| **GC Time** | 1 hour |
|
|
584
|
+
| **Retry** | 2 attempts |
|
|
585
|
+
| **Window Refetch** | Disabled |
|
|
586
|
+
| **Enabled Guard** | `enabled: !!user && conditions` |
|
|
587
|
+
| **Optimistic IDs** | `temp-${Date.now()}` |
|
|
588
|
+
| **Error Handling** | Throw in queryFn, toast in onError |
|
|
589
|
+
|
|
590
|
+
## Checklist
|
|
591
|
+
|
|
592
|
+
Before finalizing data fetching:
|
|
593
|
+
|
|
594
|
+
- [ ] Uses TanStack Query (not useEffect + useState)
|
|
595
|
+
- [ ] Query key includes all cache-affecting parameters
|
|
596
|
+
- [ ] Enabled guard for conditional queries
|
|
597
|
+
- [ ] Proper error handling with user feedback
|
|
598
|
+
- [ ] Loading states with Skeleton components
|
|
599
|
+
- [ ] Mutations invalidate related queries
|
|
600
|
+
- [ ] Optimistic updates for better UX (where appropriate)
|
|
601
|
+
- [ ] No server data stored in useState
|
|
602
|
+
- [ ] No derived state in useEffect
|
|
603
|
+
|
|
604
|
+
## Related Skills
|
|
605
|
+
|
|
606
|
+
- `entity-api` - API endpoint patterns
|
|
607
|
+
- `shadcn-components` - Loading/error UI components
|
|
608
|
+
- `react-patterns` - React best practices
|