ghcopilot-hub 1.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/README.md +176 -0
- package/hub/agents/README.md +243 -0
- package/hub/agents/archiver.agent.md +231 -0
- package/hub/agents/explore.agent.md +49 -0
- package/hub/agents/implementador.agent.md +176 -0
- package/hub/agents/librarian.agent.md +34 -0
- package/hub/agents/momus.agent.md +130 -0
- package/hub/agents/oracle.agent.md +52 -0
- package/hub/agents/plan-guardian.agent.md +109 -0
- package/hub/agents/planificador.agent.md +295 -0
- package/hub/agents/test-sentinel.agent.md +106 -0
- package/hub/base/.github/copilot-instructions.md +10 -0
- package/hub/base/.github/instructions/ghcopilot-hub.instructions.md +6 -0
- package/hub/base/.github/prompts/ghcopilot-hub-maintenance.prompt.md +8 -0
- package/hub/base/.vscode/settings.json +1 -0
- package/hub/packs/base-web.json +4 -0
- package/hub/packs/nextjs-ssr.json +4 -0
- package/hub/packs/node-api.json +4 -0
- package/hub/packs/spa-tanstack.json +4 -0
- package/hub/skills/architecture-testing/SKILL.md +108 -0
- package/hub/skills/architecture-testing/references/archunitts.md +46 -0
- package/hub/skills/ghcopilot-hub-consumer/SKILL.md +115 -0
- package/hub/skills/ghcopilot-hub-consumer/references/workflow.md +39 -0
- package/hub/skills/mermaid-expert/SKILL.md +152 -0
- package/hub/skills/mermaid-expert/assets/examples/c4_model.md +121 -0
- package/hub/skills/mermaid-expert/assets/examples/flowchart.md +123 -0
- package/hub/skills/mermaid-expert/assets/examples/img/base_minimal.png +0 -0
- package/hub/skills/mermaid-expert/assets/examples/img/corporate.png +0 -0
- package/hub/skills/mermaid-expert/assets/examples/img/dark.png +0 -0
- package/hub/skills/mermaid-expert/assets/examples/img/dark_neo.png +0 -0
- package/hub/skills/mermaid-expert/assets/examples/img/default_neo.png +0 -0
- package/hub/skills/mermaid-expert/assets/examples/img/forest_corp.png +0 -0
- package/hub/skills/mermaid-expert/assets/examples/img/handdrawn.png +0 -0
- package/hub/skills/mermaid-expert/assets/examples/img/neo.png +0 -0
- package/hub/skills/mermaid-expert/assets/examples/img/neutral_sketch.png +0 -0
- package/hub/skills/mermaid-expert/assets/examples/img/retro.png +0 -0
- package/hub/skills/mermaid-expert/assets/examples/sequence.md +116 -0
- package/hub/skills/mermaid-expert/assets/examples/styles_and_looks.md +102 -0
- package/hub/skills/mermaid-expert/assets/examples/technical.md +130 -0
- package/hub/skills/mermaid-expert/assets/examples.md +57 -0
- package/hub/skills/mermaid-expert/references/cheatsheet.md +88 -0
- package/hub/skills/mermaid-expert/references/validation.md +66 -0
- package/hub/skills/react/SKILL.md +235 -0
- package/hub/skills/react/references/common-mistakes.md +518 -0
- package/hub/skills/react/references/composition-patterns.md +526 -0
- package/hub/skills/react/references/effects-patterns.md +396 -0
- package/hub/skills/react/references/react-compiler.md +268 -0
- package/hub/skills/react-hook-form/SKILL.md +291 -0
- package/hub/skills/react-hook-form/references/field-arrays.md +98 -0
- package/hub/skills/react-hook-form/references/integration.md +102 -0
- package/hub/skills/react-hook-form/references/performance.md +96 -0
- package/hub/skills/skill-creator/SKILL.md +152 -0
- package/hub/skills/skill-creator/assets/SKILL-TEMPLATE.md +84 -0
- package/hub/skills/skill-judge/README.md +261 -0
- package/hub/skills/skill-judge/SKILL.md +806 -0
- package/hub/skills/tailwind/SKILL.md +200 -0
- package/hub/skills/tanstack/SKILL.md +284 -0
- package/hub/skills/tanstack/references/loader-adapter-examples.md +79 -0
- package/hub/skills/tanstack/references/query-options-examples.md +115 -0
- package/hub/skills/tanstack/references/resilience-patterns.md +110 -0
- package/hub/skills/tanstack/references/suspense-consumption-examples.md +82 -0
- package/hub/skills/tanstack-query/SKILL.md +241 -0
- package/hub/skills/tanstack-query/references/advanced-hooks.md +126 -0
- package/hub/skills/tanstack-query/references/best-practices.md +241 -0
- package/hub/skills/tanstack-query/references/cache-strategies.md +474 -0
- package/hub/skills/tanstack-query/references/common-patterns.md +239 -0
- package/hub/skills/tanstack-query/references/migration-v5.md +93 -0
- package/hub/skills/tanstack-query/references/resilience-and-mutations.md +63 -0
- package/hub/skills/tanstack-query/references/testing.md +116 -0
- package/hub/skills/tanstack-query/references/top-errors.md +148 -0
- package/hub/skills/tanstack-query/references/typescript.md +176 -0
- package/hub/skills/tanstack-router/SKILL.md +145 -0
- package/hub/skills/tanstack-router/references/code-splitting.md +31 -0
- package/hub/skills/tanstack-router/references/errors-and-boundaries.md +44 -0
- package/hub/skills/tanstack-router/references/loaders-and-preload.md +51 -0
- package/hub/skills/tanstack-router/references/navigation.md +24 -0
- package/hub/skills/tanstack-router/references/private-routes.md +169 -0
- package/hub/skills/tanstack-router/references/router-context.md +35 -0
- package/hub/skills/tanstack-router/references/search-params.md +29 -0
- package/hub/skills/tanstack-router/references/typescript.md +24 -0
- package/hub/skills/testing/SKILL.md +187 -0
- package/hub/skills/testing/references/assertions.md +64 -0
- package/hub/skills/testing/references/async-testing.md +66 -0
- package/hub/skills/testing/references/e2e-strategy.md +69 -0
- package/hub/skills/testing/references/layer-matrix.md +67 -0
- package/hub/skills/testing/references/performance.md +49 -0
- package/hub/skills/testing/references/tooling-map.md +81 -0
- package/hub/skills/testing/references/zustand-mocking.md +84 -0
- package/hub/skills/typescript/SKILL.md +232 -0
- package/hub/skills/typescript/references/perf-additional-concerns.md +248 -0
- package/hub/skills/typescript/references/perf-execution-cache-locality.md +178 -0
- package/hub/skills/typescript/references/reduce-branching.md +147 -0
- package/hub/skills/typescript/references/reduce-looping.md +203 -0
- package/hub/skills/typescript/references/style-and-types.md +171 -0
- package/hub/skills/typescript/references/type-vs-interface.md +27 -0
- package/hub/skills/zod/SKILL.md +219 -0
- package/hub/skills/zustand/SKILL.md +273 -0
- package/package.json +59 -0
- package/tooling/cli/src/bin.js +11 -0
- package/tooling/cli/src/cli.js +409 -0
- package/tooling/cli/src/lib/catalog-loader.js +191 -0
- package/tooling/cli/src/lib/constants.js +39 -0
- package/tooling/cli/src/lib/errors.js +8 -0
- package/tooling/cli/src/lib/frontmatter.js +41 -0
- package/tooling/cli/src/lib/fs-utils.js +77 -0
- package/tooling/cli/src/lib/managed-header.js +74 -0
- package/tooling/cli/src/lib/manifest.js +105 -0
- package/tooling/cli/src/lib/resolver.js +53 -0
- package/tooling/cli/src/lib/sync-engine.js +262 -0
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
# Resilience Patterns (TanStack Flow)
|
|
2
|
+
|
|
3
|
+
Load this file when implementing async UX behavior, route-level error handling, or mutation failure feedback.
|
|
4
|
+
|
|
5
|
+
## 1) Suspense + Skeleton for Initial Route Data
|
|
6
|
+
|
|
7
|
+
Use this when route data is preloaded with `ensureQueryData`.
|
|
8
|
+
|
|
9
|
+
```tsx
|
|
10
|
+
import { Suspense } from "react";
|
|
11
|
+
|
|
12
|
+
import { ProductListPage } from "@/presentation/product/ProductList.page";
|
|
13
|
+
import { ProductListSkeleton } from "@/presentation/product/components/ProductListSkeleton";
|
|
14
|
+
|
|
15
|
+
export const ProductListWithFallback = () => {
|
|
16
|
+
return (
|
|
17
|
+
<Suspense fallback={<ProductListSkeleton />}>
|
|
18
|
+
<ProductListPage />
|
|
19
|
+
</Suspense>
|
|
20
|
+
);
|
|
21
|
+
};
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Guidelines:
|
|
25
|
+
|
|
26
|
+
- Skeleton shape should mirror real layout to avoid jarring transitions.
|
|
27
|
+
- Do not add route-initial `isLoading` branches in the page if Suspense already wraps it.
|
|
28
|
+
|
|
29
|
+
## 2) Route Error Boundary (404 vs 500 + Retry)
|
|
30
|
+
|
|
31
|
+
Use this for routes that fetch remote data in `loader`.
|
|
32
|
+
|
|
33
|
+
```tsx
|
|
34
|
+
import { createFileRoute, useRouter } from "@tanstack/react-router";
|
|
35
|
+
|
|
36
|
+
import { productQueries } from "@/application/product/product.queries";
|
|
37
|
+
import { NotFoundPage } from "@/presentation/shared/components/NotFoundPage";
|
|
38
|
+
import { RouteErrorPage } from "@/presentation/shared/components/RouteErrorPage";
|
|
39
|
+
|
|
40
|
+
const isNotFoundError = (error: unknown) => error instanceof Error && error.message.includes("404");
|
|
41
|
+
|
|
42
|
+
function ProductsErrorBoundary({ error }: { error: unknown }) {
|
|
43
|
+
const router = useRouter();
|
|
44
|
+
|
|
45
|
+
if (isNotFoundError(error)) {
|
|
46
|
+
return <NotFoundPage />;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return <RouteErrorPage title="Unable to load products" onRetry={() => router.invalidate()} />;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export const Route = createFileRoute("/products")({
|
|
53
|
+
loader: async ({ context: { queryClient }, params }) => {
|
|
54
|
+
await queryClient.ensureQueryData(productQueries.detail(params.productId));
|
|
55
|
+
},
|
|
56
|
+
errorComponent: ProductsErrorBoundary,
|
|
57
|
+
component: ProductsPage,
|
|
58
|
+
});
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Guidelines:
|
|
62
|
+
|
|
63
|
+
- Throw from loader to boundary instead of swallowing errors.
|
|
64
|
+
- Classify not-found paths separately from generic failures.
|
|
65
|
+
- Retry should invalidate route/query state, not force full page reload.
|
|
66
|
+
|
|
67
|
+
## 3) Mutation Error Feedback without Page Crash
|
|
68
|
+
|
|
69
|
+
Use this for write operations (forms, toggles, destructive actions).
|
|
70
|
+
|
|
71
|
+
```typescript
|
|
72
|
+
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
|
73
|
+
|
|
74
|
+
import { productKeys } from "@/application/product/product.queries";
|
|
75
|
+
import { ProductRepository } from "@/infrastructure/product/product.repository";
|
|
76
|
+
|
|
77
|
+
export const useUpdateProduct = (
|
|
78
|
+
onSuccessFeedback: (message: string) => void,
|
|
79
|
+
onErrorFeedback: (message: string) => void
|
|
80
|
+
) => {
|
|
81
|
+
const queryClient = useQueryClient();
|
|
82
|
+
|
|
83
|
+
return useMutation({
|
|
84
|
+
mutationFn: ProductRepository.update,
|
|
85
|
+
onSuccess: async (updated) => {
|
|
86
|
+
await queryClient.invalidateQueries({
|
|
87
|
+
queryKey: productKeys.detail(updated.id),
|
|
88
|
+
});
|
|
89
|
+
onSuccessFeedback("Product updated");
|
|
90
|
+
},
|
|
91
|
+
onError: () => {
|
|
92
|
+
onErrorFeedback("Failed to update product. Please retry.");
|
|
93
|
+
},
|
|
94
|
+
});
|
|
95
|
+
};
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Guidelines:
|
|
99
|
+
|
|
100
|
+
- Keep user-entered state when mutation fails.
|
|
101
|
+
- Keep feedback adapters (toast/snackbar) in Presentation and inject callbacks.
|
|
102
|
+
- Prefer focused feedback (toast/inline message) over global crash UI.
|
|
103
|
+
- Retry should be user-driven and explicit.
|
|
104
|
+
|
|
105
|
+
## Decision Checklist
|
|
106
|
+
|
|
107
|
+
- Is this route-initial data? Use Suspense + boundary.
|
|
108
|
+
- Is this a user write action? Use mutation `onError` feedback.
|
|
109
|
+
- Is this a recoverable not-found case? Route-level typed not-found UI.
|
|
110
|
+
- Is this transient infra failure? Show retry action and preserve user context.
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# Suspense Consumption Examples
|
|
2
|
+
|
|
3
|
+
Use these examples in `src/application/{feature}/hooks` and `src/presentation/{feature}`.
|
|
4
|
+
|
|
5
|
+
## A. Application Hook for Route Search Data
|
|
6
|
+
|
|
7
|
+
```typescript
|
|
8
|
+
import { useSuspenseQuery } from "@tanstack/react-query";
|
|
9
|
+
import { getRouteApi } from "@tanstack/react-router";
|
|
10
|
+
|
|
11
|
+
import { productQueries } from "@/application/product/product.queries";
|
|
12
|
+
import { usePreferencesStore } from "@/application/product/store/preferences.store";
|
|
13
|
+
|
|
14
|
+
const productsRouteApi = getRouteApi("/products/");
|
|
15
|
+
|
|
16
|
+
export const useProductsFromRoute = () => {
|
|
17
|
+
const search = productsRouteApi.useSearch();
|
|
18
|
+
const storeId = usePreferencesStore((state) => state.storeId);
|
|
19
|
+
|
|
20
|
+
if (!storeId) {
|
|
21
|
+
throw new Error("storeId is required to fetch products");
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return useSuspenseQuery(productQueries.list(storeId, search));
|
|
25
|
+
};
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## B. Presentation Page Consuming Application Hook
|
|
29
|
+
|
|
30
|
+
```tsx
|
|
31
|
+
import { useProductsFromRoute } from "@/application/product/hooks/useProductsFromRoute";
|
|
32
|
+
|
|
33
|
+
export const ProductListPage = () => {
|
|
34
|
+
const { data } = useProductsFromRoute();
|
|
35
|
+
|
|
36
|
+
return (
|
|
37
|
+
<section>
|
|
38
|
+
<h1>Products</h1>
|
|
39
|
+
<ul>
|
|
40
|
+
{data.items.map((product) => (
|
|
41
|
+
<li key={product.id}>{product.name}</li>
|
|
42
|
+
))}
|
|
43
|
+
</ul>
|
|
44
|
+
</section>
|
|
45
|
+
);
|
|
46
|
+
};
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## C. Partial Refresh Pattern
|
|
50
|
+
|
|
51
|
+
When the page has route-critical data and local UI interactions:
|
|
52
|
+
|
|
53
|
+
- Keep initial data preload in loader + `ensureQueryData`.
|
|
54
|
+
- Use query-keyed local controls for client-side refetches.
|
|
55
|
+
- Avoid returning to manual `isLoading` guards for first render.
|
|
56
|
+
|
|
57
|
+
## D. Suspense Rules
|
|
58
|
+
|
|
59
|
+
- Prefer Suspense path for first route render data.
|
|
60
|
+
- Keep error handling in route boundaries or route-level components.
|
|
61
|
+
- Reserve direct presentation-level `useSuspenseQuery` for very simple pages.
|
|
62
|
+
|
|
63
|
+
## E. Route Entry Fallback Composition
|
|
64
|
+
|
|
65
|
+
```tsx
|
|
66
|
+
import { Suspense } from "react";
|
|
67
|
+
|
|
68
|
+
import { ProductListPage } from "@/presentation/product/ProductList.page";
|
|
69
|
+
import { ProductListSkeleton } from "@/presentation/product/components/ProductListSkeleton";
|
|
70
|
+
|
|
71
|
+
export const ProductListRouteView = () => (
|
|
72
|
+
<Suspense fallback={<ProductListSkeleton />}>
|
|
73
|
+
<ProductListPage />
|
|
74
|
+
</Suspense>
|
|
75
|
+
);
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Guidelines:
|
|
79
|
+
|
|
80
|
+
- Keep fallback shape aligned to page layout.
|
|
81
|
+
- Avoid parallel manual `isLoading` branches for the same initial route data.
|
|
82
|
+
- See [resilience-patterns.md](resilience-patterns.md) for error boundary and retry patterns.
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: tanstack-query
|
|
3
|
+
description: >
|
|
4
|
+
Advanced TanStack Query v5 extensions. Trigger: when implementing advanced Query patterns, v5 migrations, cache
|
|
5
|
+
optimizations, mutation lifecycle handling, or query error propagation strategies.
|
|
6
|
+
license: Apache-2.0
|
|
7
|
+
metadata:
|
|
8
|
+
author: jmgomezdev
|
|
9
|
+
version: "1.0"
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## Required Base
|
|
13
|
+
|
|
14
|
+
This skill **always** applies **after** the tanstack skill and **never** contradicts it.
|
|
15
|
+
|
|
16
|
+
- Direct reference: use the tanstack skill as the mandatory foundation.
|
|
17
|
+
- Everything not mentioned here follows tanstack rules.
|
|
18
|
+
|
|
19
|
+
## Objective
|
|
20
|
+
|
|
21
|
+
Provide advanced rules, v5 issue prevention, and cache optimization patterns that complement the
|
|
22
|
+
“render-as-you-fetch” flow defined in tanstack.
|
|
23
|
+
|
|
24
|
+
## Dependencies
|
|
25
|
+
|
|
26
|
+
- Base skill: tanstack
|
|
27
|
+
- Packages: @tanstack/react-query
|
|
28
|
+
|
|
29
|
+
## Render-as-you-fetch Alignment (tanstack)
|
|
30
|
+
|
|
31
|
+
This skill **extends** the render-as-you-fetch flow defined in the tanstack skill:
|
|
32
|
+
|
|
33
|
+
- Define `queryOptions` / `infiniteQueryOptions` in Application.
|
|
34
|
+
- Use Router loaders with `queryClient.ensureQueryData` / `ensureInfiniteQueryData`.
|
|
35
|
+
- Prefer consuming in Presentation via an Application hook that wraps `useSuspenseQuery` /
|
|
36
|
+
`useSuspenseInfiniteQuery`. Use direct hooks only for very simple cases.
|
|
37
|
+
- Never fetch server data in `useEffect`.
|
|
38
|
+
|
|
39
|
+
## References
|
|
40
|
+
|
|
41
|
+
- Base tanstack skill: [.github/skills/tanstack/SKILL.md](../tanstack/SKILL.md)
|
|
42
|
+
- Advanced patterns and hooks: [references/advanced-hooks.md](references/advanced-hooks.md)
|
|
43
|
+
- Cache strategies: [references/cache-strategies.md](references/cache-strategies.md)
|
|
44
|
+
- Best practices: [references/best-practices.md](references/best-practices.md)
|
|
45
|
+
- Common patterns: [references/common-patterns.md](references/common-patterns.md)
|
|
46
|
+
- Top errors & fixes: [references/top-errors.md](references/top-errors.md)
|
|
47
|
+
- Resilience and mutations: [references/resilience-and-mutations.md](references/resilience-and-mutations.md)
|
|
48
|
+
- TypeScript patterns: [references/typescript.md](references/typescript.md)
|
|
49
|
+
- v4 → v5 migration: [references/migration-v5.md](references/migration-v5.md)
|
|
50
|
+
- Testing: [references/testing.md](references/testing.md)
|
|
51
|
+
|
|
52
|
+
## Reference Router (Mandatory Loading)
|
|
53
|
+
|
|
54
|
+
Loading protocol:
|
|
55
|
+
|
|
56
|
+
1. Pick one scenario first.
|
|
57
|
+
2. MANDATORY: read only the selected reference before implementing.
|
|
58
|
+
3. Do NOT load all references by default.
|
|
59
|
+
4. If blocked, load exactly one additional reference.
|
|
60
|
+
|
|
61
|
+
| Scenario | MANDATORY Reference | Do NOT Load (unless needed) |
|
|
62
|
+
| ---------------------------------------------------------------------------- | -------------------------------------------------------------------------------- | --------------------------- |
|
|
63
|
+
| Advanced hook composition (`useQueries`, `useMutationState`, infinite hooks) | [references/advanced-hooks.md](references/advanced-hooks.md) | `migration-v5.md` |
|
|
64
|
+
| Cache lifetimes, invalidation, refetch strategy | [references/cache-strategies.md](references/cache-strategies.md) | `migration-v5.md` |
|
|
65
|
+
| v5 migration and API breakages | [references/migration-v5.md](references/migration-v5.md) | `advanced-hooks.md` |
|
|
66
|
+
| Query errors, callback confusion, common pitfalls | [references/top-errors.md](references/top-errors.md) | `testing.md` |
|
|
67
|
+
| Mutation resilience and feedback ownership | [references/resilience-and-mutations.md](references/resilience-and-mutations.md) | `migration-v5.md` |
|
|
68
|
+
| Query typing constraints and TS inference | [references/typescript.md](references/typescript.md) | `cache-strategies.md` |
|
|
69
|
+
|
|
70
|
+
## Compatibility Checklist with tanstack
|
|
71
|
+
|
|
72
|
+
Before applying this skill, confirm:
|
|
73
|
+
|
|
74
|
+
1. Use of queryOptions and loaders per tanstack.
|
|
75
|
+
2. Object syntax in hooks.
|
|
76
|
+
3. Query keys as arrays.
|
|
77
|
+
4. Suspense + loaders for required data.
|
|
78
|
+
|
|
79
|
+
## Quick Application (Summary)
|
|
80
|
+
|
|
81
|
+
- `useMutationState` for global tracking.
|
|
82
|
+
- `networkMode` for offline/PWA.
|
|
83
|
+
- `useQueries` with `combine` for aggregated parallel results.
|
|
84
|
+
- `infiniteQueryOptions` and `maxPages` for efficient pagination.
|
|
85
|
+
- v5 issue prevention (removed callbacks, `gcTime`, `isPending`).
|
|
86
|
+
|
|
87
|
+
## Essential Rules (Must)
|
|
88
|
+
|
|
89
|
+
- Use object syntax and array query keys everywhere.
|
|
90
|
+
- Keep route-required data in the tanstack flow (Application `queryOptions` → Loader `ensureQueryData` →
|
|
91
|
+
Presentation `useSuspenseQuery`).
|
|
92
|
+
- Set `staleTime` by data volatility and `gcTime` by revisit frequency.
|
|
93
|
+
- Invalidate with precise keys after mutations; use `refetchType: 'all'` only when inactive queries must refetch.
|
|
94
|
+
- Optimistic updates require: `cancelQueries` → snapshot → optimistic `setQueryData` → rollback on error →
|
|
95
|
+
invalidate on settle.
|
|
96
|
+
- Infinite queries must set `initialPageParam`; `maxPages` requires bi-directional pagination.
|
|
97
|
+
- Use `useQueries` for optional/secondary data, not required route data.
|
|
98
|
+
- Use `prefetchQuery` only for optional navigation (hover/background), never for required route data.
|
|
99
|
+
- Keep mutation feedback layer-safe: inject feedback callbacks or return mutation state; do not import presentation
|
|
100
|
+
toasts in Application hooks.
|
|
101
|
+
|
|
102
|
+
## TanStack Query v5 — Critical Rules, Issues, and Anti‑Patterns
|
|
103
|
+
|
|
104
|
+
> These rules **extend** the tanstack skill. If there is a conflict, tanstack wins.
|
|
105
|
+
|
|
106
|
+
### Critical Rules (Always Do)
|
|
107
|
+
|
|
108
|
+
✅ Use object syntax in all hooks:
|
|
109
|
+
|
|
110
|
+
```tsx
|
|
111
|
+
useQuery({ queryKey, queryFn, ...options });
|
|
112
|
+
useMutation({ mutationFn, ...options });
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
✅ Query keys always as arrays:
|
|
116
|
+
|
|
117
|
+
```tsx
|
|
118
|
+
queryKey: ["todos"];
|
|
119
|
+
queryKey: ["todos", id];
|
|
120
|
+
queryKey: ["todos", { filter }];
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
✅ Configure `staleTime` appropriately for the use case.
|
|
124
|
+
|
|
125
|
+
✅ Use `isPending` for initial loading state.
|
|
126
|
+
|
|
127
|
+
✅ Always throw `Error` in `queryFn` on failure.
|
|
128
|
+
|
|
129
|
+
✅ Invalidate queries after mutations with `queryClient.invalidateQueries`.
|
|
130
|
+
|
|
131
|
+
✅ Use `queryOptions` for reusable patterns.
|
|
132
|
+
|
|
133
|
+
✅ Use `gcTime`, not `cacheTime`.
|
|
134
|
+
|
|
135
|
+
### Forbidden (Never Do)
|
|
136
|
+
|
|
137
|
+
❌ v4 syntax (array/function).
|
|
138
|
+
|
|
139
|
+
❌ Callbacks in queries (`onSuccess`, `onError`, `onSettled`) - this does not apply to mutations.
|
|
140
|
+
|
|
141
|
+
❌ `cacheTime`, `isLoading` for initial state, `keepPreviousData`, `useErrorBoundary`.
|
|
142
|
+
|
|
143
|
+
❌ Using `enabled` with `useSuspenseQuery`.
|
|
144
|
+
|
|
145
|
+
❌ Omitting `initialPageParam` in infinite queries.
|
|
146
|
+
|
|
147
|
+
❌ Relying on `refetchOnMount: false` for errored queries (use `retryOnMount: false`).
|
|
148
|
+
|
|
149
|
+
### Known Issues and Prevention
|
|
150
|
+
|
|
151
|
+
#### #2 Query callbacks removed
|
|
152
|
+
|
|
153
|
+
- **Problem:** callbacks do not exist in v5 queries.
|
|
154
|
+
- **Prevention:** use `useEffect` with `data`.
|
|
155
|
+
- **Note:** mutation callbacks remain valid and are expected for optimistic flows and feedback.
|
|
156
|
+
|
|
157
|
+
#### #3 `status: loading` → `pending`
|
|
158
|
+
|
|
159
|
+
- **Problem:** UI out of sync.
|
|
160
|
+
- **Prevention:** use `isPending` for initial load.
|
|
161
|
+
|
|
162
|
+
#### #4 `cacheTime` → `gcTime`
|
|
163
|
+
|
|
164
|
+
- **Problem:** type errors.
|
|
165
|
+
- **Prevention:** replace with `gcTime`.
|
|
166
|
+
|
|
167
|
+
#### #5 `useSuspenseQuery` + `enabled`
|
|
168
|
+
|
|
169
|
+
- **Problem:** `enabled` not available.
|
|
170
|
+
- **Prevention:** conditional rendering outside the hook.
|
|
171
|
+
|
|
172
|
+
#### #6 `initialPageParam` required
|
|
173
|
+
|
|
174
|
+
- **Problem:** type errors.
|
|
175
|
+
- **Prevention:** set `initialPageParam` explicitly.
|
|
176
|
+
|
|
177
|
+
#### #7 `keepPreviousData` removed
|
|
178
|
+
|
|
179
|
+
- **Prevention:** `placeholderData: keepPreviousData`.
|
|
180
|
+
|
|
181
|
+
#### #8 Default Error type
|
|
182
|
+
|
|
183
|
+
- **Prevention:** throw `Error` or type the error explicitly.
|
|
184
|
+
|
|
185
|
+
#### #12 Mutation callback signature change (v5.89.0+)
|
|
186
|
+
|
|
187
|
+
- **Problem:** callbacks receive 4 parameters.
|
|
188
|
+
- **Prevention:** use `(data, variables, onMutateResult, context)` signature.
|
|
189
|
+
|
|
190
|
+
#### #13 Readonly query keys break partial matching (v5.90.8)
|
|
191
|
+
|
|
192
|
+
- **Prevention:** upgrade to v5.90.9+.
|
|
193
|
+
|
|
194
|
+
#### #14 `useMutationState` loses inference
|
|
195
|
+
|
|
196
|
+
- **Prevention:** cast in `select`.
|
|
197
|
+
|
|
198
|
+
#### #15 Cancellation in StrictMode with `fetchQuery`
|
|
199
|
+
|
|
200
|
+
- **Prevention:** expected behavior; use `staleTime: Infinity` to keep it observed.
|
|
201
|
+
|
|
202
|
+
#### #16 `invalidateQueries` only refetches active queries
|
|
203
|
+
|
|
204
|
+
- **Prevention:** use `refetchType: 'all'` if you need inactive ones.
|
|
205
|
+
|
|
206
|
+
### Community Tips
|
|
207
|
+
|
|
208
|
+
#### Different options in the same query
|
|
209
|
+
|
|
210
|
+
- “Last write wins” for future options.
|
|
211
|
+
- **Recommended:** compute options on render using functions.
|
|
212
|
+
|
|
213
|
+
#### `refetch()` is not for new parameters
|
|
214
|
+
|
|
215
|
+
- **Rule:** change parameters in the `queryKey` and let Query refetch.
|
|
216
|
+
|
|
217
|
+
### Anti‑Patterns (FORBIDDEN)
|
|
218
|
+
|
|
219
|
+
```ts
|
|
220
|
+
// Do not store server state in Zustand/Redux
|
|
221
|
+
// Do not use string query keys
|
|
222
|
+
// Do not use cacheTime
|
|
223
|
+
// Do not use isLoading for initial state
|
|
224
|
+
// Do not invalidate EVERYTHING indiscriminately
|
|
225
|
+
// Do not forget cancelQueries in optimistic updates
|
|
226
|
+
// Do not mutate cache data directly
|
|
227
|
+
// Do not fetch in useEffect
|
|
228
|
+
// Do not import presentation feedback adapters (toast/snackbar) into application mutation hooks
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
> For complete examples, see the tanstack skill.
|
|
232
|
+
|
|
233
|
+
## Quality Gate (Self-Check)
|
|
234
|
+
|
|
235
|
+
Before finishing, verify:
|
|
236
|
+
|
|
237
|
+
- Query hooks use v5 object syntax and array query keys.
|
|
238
|
+
- Route-critical reads still follow loader + Suspense flow from `tanstack`.
|
|
239
|
+
- Query callbacks are not used (mutations may use callbacks).
|
|
240
|
+
- Mutation feedback does not import presentation adapters into Application hooks.
|
|
241
|
+
- Invalidation uses key factories and avoids broad indiscriminate invalidation.
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
# Advanced TanStack Query Patterns (v5)
|
|
2
|
+
|
|
3
|
+
> These practices **complement** the tanstack skill. They do not replace or contradict its rules.
|
|
4
|
+
|
|
5
|
+
## useMutationState — Global mutation tracking
|
|
6
|
+
|
|
7
|
+
Access mutation state from any component without prop drilling:
|
|
8
|
+
|
|
9
|
+
```tsx
|
|
10
|
+
import { useMutationState } from "@tanstack/react-query";
|
|
11
|
+
|
|
12
|
+
function GlobalLoadingIndicator() {
|
|
13
|
+
const pendingMutations = useMutationState({
|
|
14
|
+
filters: { status: "pending" },
|
|
15
|
+
select: (mutation) => mutation.state.variables,
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
if (pendingMutations.length === 0) return null;
|
|
19
|
+
return <div>Saving {pendingMutations.length} items...</div>;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Filter by mutationKey
|
|
23
|
+
const todoMutations = useMutationState({
|
|
24
|
+
filters: { mutationKey: ["addTodo"] },
|
|
25
|
+
});
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
### Typing note
|
|
29
|
+
|
|
30
|
+
Due to fuzzy inference, `mutation.state.variables` can be `unknown`. See issue #14 in the tanstack-query SKILL.md.
|
|
31
|
+
|
|
32
|
+
## Network Mode — Offline/PWA support
|
|
33
|
+
|
|
34
|
+
Control behavior when offline:
|
|
35
|
+
|
|
36
|
+
```tsx
|
|
37
|
+
const queryClient = new QueryClient({
|
|
38
|
+
defaultOptions: {
|
|
39
|
+
queries: {
|
|
40
|
+
networkMode: "offlineFirst",
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
useQuery({
|
|
46
|
+
queryKey: ["todos"],
|
|
47
|
+
queryFn: fetchTodos,
|
|
48
|
+
networkMode: "always",
|
|
49
|
+
});
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
| Mode | Behavior |
|
|
53
|
+
| ------------------ | ------------------------------------------- |
|
|
54
|
+
| `online` (default) | Only fetches when online |
|
|
55
|
+
| `always` | Always tries (local APIs or service worker) |
|
|
56
|
+
| `offlineFirst` | Uses cache first, fetches when back online |
|
|
57
|
+
|
|
58
|
+
**Paused state detection:**
|
|
59
|
+
|
|
60
|
+
```tsx
|
|
61
|
+
const { isPending, fetchStatus } = useQuery(...)
|
|
62
|
+
// isPending + fetchStatus === 'paused' = offline, waiting for network
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## useQueries with combine
|
|
66
|
+
|
|
67
|
+
Combine results from parallel queries (use for optional/secondary data, not required route data):
|
|
68
|
+
|
|
69
|
+
```tsx
|
|
70
|
+
const results = useQueries({
|
|
71
|
+
queries: userIds.map((id) => ({
|
|
72
|
+
queryKey: ['user', id],
|
|
73
|
+
queryFn: () => fetchUser(id),
|
|
74
|
+
})),
|
|
75
|
+
combine: (results) => ({
|
|
76
|
+
data: results.map((r) => r.data),
|
|
77
|
+
pending: results.some((r) => r.isPending),
|
|
78
|
+
error: results.find((r) => r.error)?.error,
|
|
79
|
+
}),
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
if (results.pending) return <Loading />;
|
|
83
|
+
console.log(results.data);
|
|
84
|
+
|
|
85
|
+
> For required route data, keep the tanstack render-as-you-fetch flow: Application `queryOptions` → Loader `ensureQueryData` → Presentation `useSuspenseQuery`.
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## infiniteQueryOptions helper
|
|
89
|
+
|
|
90
|
+
Typed factory for infinite queries (parallel to queryOptions):
|
|
91
|
+
|
|
92
|
+
```tsx
|
|
93
|
+
import {
|
|
94
|
+
infiniteQueryOptions,
|
|
95
|
+
prefetchInfiniteQuery,
|
|
96
|
+
useInfiniteQuery,
|
|
97
|
+
} from "@tanstack/react-query";
|
|
98
|
+
|
|
99
|
+
const todosInfiniteOptions = infiniteQueryOptions({
|
|
100
|
+
queryKey: ["todos", "infinite"],
|
|
101
|
+
queryFn: ({ pageParam }) => fetchTodosPage(pageParam),
|
|
102
|
+
initialPageParam: 0,
|
|
103
|
+
getNextPageParam: (lastPage) => lastPage.nextCursor,
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
useInfiniteQuery(todosInfiniteOptions);
|
|
107
|
+
useSuspenseInfiniteQuery(todosInfiniteOptions);
|
|
108
|
+
prefetchInfiniteQuery(queryClient, todosInfiniteOptions);
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
## maxPages — Memory optimization
|
|
112
|
+
|
|
113
|
+
Limit cached pages for infinite queries:
|
|
114
|
+
|
|
115
|
+
```tsx
|
|
116
|
+
useInfiniteQuery({
|
|
117
|
+
queryKey: ["posts"],
|
|
118
|
+
queryFn: ({ pageParam }) => fetchPosts(pageParam),
|
|
119
|
+
initialPageParam: 0,
|
|
120
|
+
getNextPageParam: (lastPage) => lastPage.nextCursor,
|
|
121
|
+
getPreviousPageParam: (firstPage) => firstPage.prevCursor,
|
|
122
|
+
maxPages: 3,
|
|
123
|
+
});
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
**Rule:** `maxPages` requires bi-directional pagination.
|