@hitch42/applet 0.1.1-beta.1 → 0.1.1-beta.10
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/dist/_internal/client.js +120 -11
- package/dist/_internal/log-context.d.ts +34 -0
- package/dist/_internal/log-context.js +2 -0
- package/dist/_internal/server.d.ts +14 -1
- package/dist/_internal/server.js +122 -118
- package/dist/applet.js +53 -29
- package/dist/build-Bh_S5BUv.js +2 -0
- package/dist/{build-Rk3xDZV7.js → build-CEfFoIZZ.js} +30 -14
- package/dist/ci-BKw14X2h.js +69 -0
- package/dist/client.d.ts +3 -3
- package/dist/{contract-DMrpbaLJ.d.ts → contract-CsUpTBe0.d.ts} +1 -1
- package/dist/{deploy-D3BGDqgC.js → deploy-wGYjwXrx.js} +249 -35
- package/dist/email-DBTVhNB_.js +62 -0
- package/dist/{generate-CH0VQmUi.js → generate-BEIYs17x.js} +79 -25
- package/dist/generate-Dox-OYt6.js +2 -0
- package/dist/headers-DkNr9MLJ.js +17 -0
- package/dist/index.d.ts +123 -11
- package/dist/index.js +142 -26
- package/dist/{init-BS1Y-eNL.js → init-6oGPM46R.js} +17 -18
- package/dist/load-config-xEdTXs0y.js +27 -0
- package/dist/log-context-zu8U9Sjm.js +73 -0
- package/dist/login-CUx3TmFQ.js +12 -0
- package/dist/{logs-m00pzr_O.js → logs-DSjuEN0Q.js} +20 -22
- package/dist/{manifest-BQCAxATS.d.ts → manifest-C6_F4bL8.d.ts} +9 -0
- package/dist/{manifest-DyoJ1MIL.js → manifest-CJSL9GuC.js} +25 -3
- package/dist/me-BW7Paary.js +32 -0
- package/dist/{project-CxN-Dtc7.js → project-BRZGd6xq.js} +227 -34
- package/dist/prompt-ChS-grYO.js +29 -0
- package/dist/query-cache-CXmAyLv3.js +82 -0
- package/dist/react.d.ts +3 -2
- package/dist/react.js +98 -80
- package/dist/schedules-C8_Svr4h.js +42 -0
- package/dist/server.d.ts +9 -4
- package/dist/server.js +29 -8
- package/dist/telephony-protocol-D-nf3Gca.d.ts +76 -0
- package/dist/telephony.d.ts +97 -0
- package/dist/telephony.js +726 -0
- package/dist/{upload-YbRKEqR6.d.ts → upload-DKZzz5kW.d.ts} +6 -6
- package/dist/{variables-CP7Ce3Np.js → variables-DT5RyHdH.js} +14 -39
- package/dist/vite.d.ts +3 -60
- package/dist/vite.js +503 -34
- package/dist/workflow-engine-BOTzURKM.js +111 -0
- package/dist/{auth-DaVK0k5R.js → workspace-D0dnnlxL.js} +75 -40
- package/package.json +19 -6
- package/scaffold/.agents/skills/hitch-applets/SKILL.md +60 -15
- package/scaffold/.agents/skills/hitch-applets/references/email-tracking.md +49 -0
- package/scaffold/.agents/skills/hitch-applets/references/files.md +3 -3
- package/scaffold/.agents/skills/hitch-applets/references/manifest.md +29 -2
- package/scaffold/.agents/skills/hitch-applets/references/markdown.md +2 -0
- package/scaffold/.agents/skills/hitch-applets/references/performance.md +74 -0
- package/scaffold/.agents/skills/hitch-applets/references/records.md +32 -8
- package/scaffold/.agents/skills/hitch-applets/references/schedules.md +7 -2
- package/scaffold/.agents/skills/hitch-applets/references/server.md +0 -3
- package/scaffold/.agents/skills/hitch-applets/references/workflows.md +11 -3
- package/scaffold/.agents/skills/hitch-cli/SKILL.md +129 -0
- package/scaffold/.agents/skills/hitch-data/SKILL.md +52 -0
- package/scaffold/.agents/skills/hitch-data/references/attributes.md +127 -0
- package/scaffold/.agents/skills/hitch-data/references/limits.md +64 -0
- package/scaffold/.agents/skills/hitch-theory/SKILL.md +151 -0
- package/scaffold/.agents/skills/react-best-practices/SKILL.md +127 -0
- package/scaffold/.agents/skills/react-best-practices/rules/advanced-effect-event-deps.md +50 -0
- package/scaffold/.agents/skills/react-best-practices/rules/advanced-event-handler-refs.md +55 -0
- package/scaffold/.agents/skills/react-best-practices/rules/advanced-init-once.md +42 -0
- package/scaffold/.agents/skills/react-best-practices/rules/advanced-use-latest.md +39 -0
- package/scaffold/.agents/skills/react-best-practices/rules/async-cheap-condition-before-await.md +37 -0
- package/scaffold/.agents/skills/react-best-practices/rules/async-defer-await.md +82 -0
- package/scaffold/.agents/skills/react-best-practices/rules/async-dependencies.md +48 -0
- package/scaffold/.agents/skills/react-best-practices/rules/async-parallel.md +24 -0
- package/scaffold/.agents/skills/react-best-practices/rules/bundle-analyzable-paths.md +42 -0
- package/scaffold/.agents/skills/react-best-practices/rules/bundle-barrel-imports.md +39 -0
- package/scaffold/.agents/skills/react-best-practices/rules/bundle-conditional.md +35 -0
- package/scaffold/.agents/skills/react-best-practices/rules/bundle-defer-third-party.md +46 -0
- package/scaffold/.agents/skills/react-best-practices/rules/bundle-dynamic-imports.md +38 -0
- package/scaffold/.agents/skills/react-best-practices/rules/bundle-preload.md +40 -0
- package/scaffold/.agents/skills/react-best-practices/rules/client-event-listeners.md +78 -0
- package/scaffold/.agents/skills/react-best-practices/rules/client-localstorage-schema.md +74 -0
- package/scaffold/.agents/skills/react-best-practices/rules/client-passive-event-listeners.md +48 -0
- package/scaffold/.agents/skills/react-best-practices/rules/js-batch-dom-css.md +110 -0
- package/scaffold/.agents/skills/react-best-practices/rules/js-cache-function-results.md +58 -0
- package/scaffold/.agents/skills/react-best-practices/rules/js-cache-property-access.md +28 -0
- package/scaffold/.agents/skills/react-best-practices/rules/js-cache-storage.md +56 -0
- package/scaffold/.agents/skills/react-best-practices/rules/js-combine-iterations.md +32 -0
- package/scaffold/.agents/skills/react-best-practices/rules/js-early-exit.md +50 -0
- package/scaffold/.agents/skills/react-best-practices/rules/js-flatmap-filter.md +51 -0
- package/scaffold/.agents/skills/react-best-practices/rules/js-hoist-regexp.md +45 -0
- package/scaffold/.agents/skills/react-best-practices/rules/js-index-maps.md +37 -0
- package/scaffold/.agents/skills/react-best-practices/rules/js-length-check-first.md +50 -0
- package/scaffold/.agents/skills/react-best-practices/rules/js-min-max-loop.md +82 -0
- package/scaffold/.agents/skills/react-best-practices/rules/js-request-idle-callback.md +104 -0
- package/scaffold/.agents/skills/react-best-practices/rules/js-set-map-lookups.md +24 -0
- package/scaffold/.agents/skills/react-best-practices/rules/js-tosorted-immutable.md +57 -0
- package/scaffold/.agents/skills/react-best-practices/rules/rendering-activity.md +26 -0
- package/scaffold/.agents/skills/react-best-practices/rules/rendering-animate-svg-wrapper.md +38 -0
- package/scaffold/.agents/skills/react-best-practices/rules/rendering-conditional-render.md +32 -0
- package/scaffold/.agents/skills/react-best-practices/rules/rendering-content-visibility.md +38 -0
- package/scaffold/.agents/skills/react-best-practices/rules/rendering-hoist-jsx.md +36 -0
- package/scaffold/.agents/skills/react-best-practices/rules/rendering-resource-hints.md +81 -0
- package/scaffold/.agents/skills/react-best-practices/rules/rendering-script-defer-async.md +36 -0
- package/scaffold/.agents/skills/react-best-practices/rules/rendering-svg-precision.md +28 -0
- package/scaffold/.agents/skills/react-best-practices/rules/rendering-usetransition-loading.md +43 -0
- package/scaffold/.agents/skills/react-best-practices/rules/rerender-defer-reads.md +39 -0
- package/scaffold/.agents/skills/react-best-practices/rules/rerender-dependencies.md +45 -0
- package/scaffold/.agents/skills/react-best-practices/rules/rerender-derived-state-no-effect.md +40 -0
- package/scaffold/.agents/skills/react-best-practices/rules/rerender-derived-state.md +29 -0
- package/scaffold/.agents/skills/react-best-practices/rules/rerender-functional-setstate.md +77 -0
- package/scaffold/.agents/skills/react-best-practices/rules/rerender-lazy-state-init.md +56 -0
- package/scaffold/.agents/skills/react-best-practices/rules/rerender-memo-with-default-value.md +36 -0
- package/scaffold/.agents/skills/react-best-practices/rules/rerender-memo.md +44 -0
- package/scaffold/.agents/skills/react-best-practices/rules/rerender-move-effect-to-event.md +45 -0
- package/scaffold/.agents/skills/react-best-practices/rules/rerender-no-inline-components.md +75 -0
- package/scaffold/.agents/skills/react-best-practices/rules/rerender-simple-expression-in-memo.md +35 -0
- package/scaffold/.agents/skills/react-best-practices/rules/rerender-split-combined-hooks.md +64 -0
- package/scaffold/.agents/skills/react-best-practices/rules/rerender-transitions.md +40 -0
- package/scaffold/.agents/skills/react-best-practices/rules/rerender-use-deferred-value.md +59 -0
- package/scaffold/.agents/skills/react-best-practices/rules/rerender-use-ref-transient-values.md +73 -0
- package/scaffold/.agents/skills/tanstack-query/SKILL.md +136 -0
- package/scaffold/.agents/skills/tanstack-query/references/compositions/automatic-invalidation-after-mutations.md +124 -0
- package/scaffold/.agents/skills/tanstack-query/references/compositions/broadcast-realtime-and-multi-tab-synchronization.md +150 -0
- package/scaffold/.agents/skills/tanstack-query/references/compositions/compose-query-with-tanstack-router-and-start.md +248 -0
- package/scaffold/.agents/skills/tanstack-query/references/compositions/enforce-query-best-practices-with-eslint.md +167 -0
- package/scaffold/.agents/skills/tanstack-query/references/compositions/persist-offline-and-restore-caches.md +181 -0
- package/scaffold/.agents/skills/tanstack-query/references/compositions/query-data-and-forms.md +156 -0
- package/scaffold/.agents/skills/tanstack-query/references/core/build-query-abstractions.md +130 -0
- package/scaffold/.agents/skills/tanstack-query/references/core/cancel-queries-and-consume-abort-signals.md +176 -0
- package/scaffold/.agents/skills/tanstack-query/references/core/concurrent-optimistic-updates.md +135 -0
- package/scaffold/.agents/skills/tanstack-query/references/core/coordinate-query-execution.md +188 -0
- package/scaffold/.agents/skills/tanstack-query/references/core/design-query-keys-and-options.md +182 -0
- package/scaffold/.agents/skills/tanstack-query/references/core/fetch-and-observe-queries.md +193 -0
- package/scaffold/.agents/skills/tanstack-query/references/core/implement-optimistic-updates-and-cache-writes.md +168 -0
- package/scaffold/.agents/skills/tanstack-query/references/core/paginate-and-build-infinite-queries.md +188 -0
- package/scaffold/.agents/skills/tanstack-query/references/core/seed-placeholder-select-and-transform-data.md +188 -0
- package/scaffold/.agents/skills/tanstack-query/references/core/selectors-and-derived-state.md +145 -0
- package/scaffold/.agents/skills/tanstack-query/references/core/tune-defaults-freshness-retries-and-refetching.md +172 -0
- package/scaffold/.agents/skills/tanstack-query/references/core/understand-query-internals-and-observers.md +104 -0
- package/scaffold/.agents/skills/tanstack-query/references/core/write-mutations-and-invalidate-related-queries.md +183 -0
- package/scaffold/.agents/skills/tanstack-query/references/framework/debug-with-devtools.md +169 -0
- package/scaffold/.agents/skills/tanstack-query/references/framework/handle-status-and-errors.md +119 -0
- package/scaffold/.agents/skills/tanstack-query/references/framework/shape-data-and-render-efficiently.md +184 -0
- package/scaffold/.agents/skills/tanstack-query/references/framework/use-framework-adapter-reactivity.md +185 -0
- package/scaffold/.agents/skills/tanstack-query/references/framework/use-suspense-and-error-boundaries.md +208 -0
- package/scaffold/.agents/skills/tanstack-query/references/lifecycle/migrate-major-versions-and-codemods.md +169 -0
- package/scaffold/.agents/skills/tanstack-query/references/lifecycle/prefetch-and-remove-request-waterfalls.md +228 -0
- package/scaffold/.agents/skills/tanstack-query/references/lifecycle/setup-query-client-and-providers.md +161 -0
- package/scaffold/.agents/skills/tanstack-query/references/lifecycle/ssr-hydration-and-streaming.md +209 -0
- package/scaffold/.agents/skills/tanstack-query/references/lifecycle/test-query-code.md +160 -0
- package/scaffold/.agents/skills/tanstack-router/SKILL.md +134 -0
- package/scaffold/.agents/skills/tanstack-router/references/compositions/router-query.md +412 -0
- package/scaffold/.agents/skills/tanstack-router/references/react-router.md +495 -0
- package/scaffold/.agents/skills/tanstack-router/references/router-core/auth-and-guards.md +495 -0
- package/scaffold/.agents/skills/tanstack-router/references/router-core/code-splitting.md +318 -0
- package/scaffold/.agents/skills/tanstack-router/references/router-core/data-loading.md +490 -0
- package/scaffold/.agents/skills/tanstack-router/references/router-core/index.md +158 -0
- package/scaffold/.agents/skills/tanstack-router/references/router-core/navigation.md +433 -0
- package/scaffold/.agents/skills/tanstack-router/references/router-core/not-found-and-errors.md +436 -0
- package/scaffold/.agents/skills/tanstack-router/references/router-core/path-params.md +383 -0
- package/scaffold/.agents/skills/tanstack-router/references/router-core/search-params.md +343 -0
- package/scaffold/.agents/skills/tanstack-router/references/router-core/ssr.md +462 -0
- package/scaffold/.agents/skills/tanstack-router/references/router-core/type-safety.md +493 -0
- package/scaffold/.agents/skills/tanstack-router/references/router-plugin.md +250 -0
- package/scaffold/.agents/skills/tanstack-router/references/virtual-file-routes.md +215 -0
- package/scaffold/.agents/skills/web-design-guidelines/SKILL.md +184 -0
- package/scaffold/.github/workflows/hitch.yml +15 -0
- package/scaffold/AGENTS.md +2 -2
- package/scaffold/applet/src/client/components/ui/alert.tsx +1 -1
- package/scaffold/applet/src/client/components/ui/button.tsx +1 -1
- package/scaffold/applet/src/client/components/ui/checkbox.tsx +1 -1
- package/scaffold/applet/src/client/components/ui/dialog.tsx +2 -2
- package/scaffold/applet/src/client/components/ui/dropdown-menu.tsx +2 -2
- package/scaffold/applet/src/client/components/ui/input-group.tsx +2 -2
- package/scaffold/applet/src/client/components/ui/select.tsx +2 -2
- package/scaffold/applet/src/client/components/ui/sheet.tsx +2 -2
- package/scaffold/applet/src/client/components/ui/spinner.tsx +2 -1
- package/scaffold/applet/src/client/components/ui/tabs.tsx +1 -1
- package/scaffold/applet/src/client/lib/utils.ts +1 -6
- package/scaffold/applet/src/client/styles/globals.css +2 -0
- package/dist/build-CEku1lyb.js +0 -2
- package/dist/generate-6Nbt7uii.js +0 -2
- package/dist/load-config-BnxocLAQ.js +0 -56
package/scaffold/.agents/skills/tanstack-query/references/core/concurrent-optimistic-updates.md
ADDED
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: core/concurrent-optimistic-updates
|
|
3
|
+
description: >
|
|
4
|
+
Use this when multiple optimistic mutations can overlap: mutation variables,
|
|
5
|
+
submittedAt keys, scoped mutationKey filters, isMutating guards, cancellation,
|
|
6
|
+
rollback context, filtered list updates, and avoiding windows of inconsistent
|
|
7
|
+
UI during concurrent writes.
|
|
8
|
+
type: core
|
|
9
|
+
library: TanStack Query
|
|
10
|
+
library_version: "5.101.0"
|
|
11
|
+
requires:
|
|
12
|
+
- core/implement-optimistic-updates-and-cache-writes
|
|
13
|
+
- core/cancel-queries-and-consume-abort-signals
|
|
14
|
+
sources:
|
|
15
|
+
- TanStack/query:docs/framework/react/guides/optimistic-updates.md
|
|
16
|
+
- TanStack/query:docs/framework/react/reference/useMutationState.md
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## Core Patterns
|
|
20
|
+
|
|
21
|
+
Concurrent optimistic updates need per-mutation identity and scoped invalidation. A rollback or invalidation from one mutation should not erase another mutation that is still pending.
|
|
22
|
+
|
|
23
|
+
### Scope related mutations
|
|
24
|
+
|
|
25
|
+
```tsx
|
|
26
|
+
const mutation = useMutation({
|
|
27
|
+
mutationKey: ["items"],
|
|
28
|
+
mutationFn: toggleItem,
|
|
29
|
+
onMutate: async ({ id }) => {
|
|
30
|
+
await queryClient.cancelQueries({ queryKey: ["items", "detail", id] });
|
|
31
|
+
const previousItem = queryClient.getQueryData<Item>(["items", "detail", id]);
|
|
32
|
+
|
|
33
|
+
queryClient.setQueryData<Item>(["items", "detail", id], (item) =>
|
|
34
|
+
item ? { ...item, isActive: !item.isActive } : item,
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
return { previousItem };
|
|
38
|
+
},
|
|
39
|
+
onError: (_error, variables, context) => {
|
|
40
|
+
queryClient.setQueryData(["items", "detail", variables.id], context?.previousItem);
|
|
41
|
+
},
|
|
42
|
+
onSettled: () => {
|
|
43
|
+
if (queryClient.isMutating({ mutationKey: ["items"] }) === 1) {
|
|
44
|
+
return queryClient.invalidateQueries({ queryKey: ["items"] });
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
});
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Use `queryClient.isMutating()` imperatively inside the callback so the count is current at the moment invalidation would run.
|
|
51
|
+
|
|
52
|
+
### Render pending variables with stable keys
|
|
53
|
+
|
|
54
|
+
```tsx
|
|
55
|
+
const pendingAdds = useMutationState<string>({
|
|
56
|
+
filters: { mutationKey: ["todos", "add"], status: "pending" },
|
|
57
|
+
select: (mutation) => `${mutation.state.submittedAt}:${mutation.state.variables}`,
|
|
58
|
+
});
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
`submittedAt` distinguishes multiple pending mutations with the same variables.
|
|
62
|
+
|
|
63
|
+
## Common Mistakes
|
|
64
|
+
|
|
65
|
+
### CRITICAL One mutation invalidation reverts another optimistic update
|
|
66
|
+
|
|
67
|
+
Wrong:
|
|
68
|
+
|
|
69
|
+
```ts
|
|
70
|
+
onSettled: () => queryClient.invalidateQueries({ queryKey: ["items"] });
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Correct:
|
|
74
|
+
|
|
75
|
+
```ts
|
|
76
|
+
onSettled: () => {
|
|
77
|
+
if (queryClient.isMutating({ mutationKey: ["items"] }) === 1) {
|
|
78
|
+
return queryClient.invalidateQueries({ queryKey: ["items"] });
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Skip intermediate invalidations while related optimistic mutations are still in flight.
|
|
84
|
+
|
|
85
|
+
### HIGH Optimistic list update ignores current filters
|
|
86
|
+
|
|
87
|
+
Wrong:
|
|
88
|
+
|
|
89
|
+
```ts
|
|
90
|
+
queryClient.setQueryData(["items", "list", filters], (items) =>
|
|
91
|
+
items?.map((item) => (item.id === updated.id ? updated : item)),
|
|
92
|
+
);
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Correct:
|
|
96
|
+
|
|
97
|
+
```ts
|
|
98
|
+
queryClient.setQueryData(["items", "list", filters], (items) =>
|
|
99
|
+
items
|
|
100
|
+
?.map((item) => (item.id === updated.id ? updated : item))
|
|
101
|
+
.filter((item) => matchesFilters(item, filters)),
|
|
102
|
+
);
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
If the server would remove the updated item from the filtered list, the optimistic cache update should do the same.
|
|
106
|
+
|
|
107
|
+
### HIGH Pending optimistic rows share unstable keys
|
|
108
|
+
|
|
109
|
+
Wrong:
|
|
110
|
+
|
|
111
|
+
```tsx
|
|
112
|
+
{
|
|
113
|
+
variables.map((title) => <li key={title}>{title}</li>);
|
|
114
|
+
}
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
Correct:
|
|
118
|
+
|
|
119
|
+
```tsx
|
|
120
|
+
const pending = useMutationState({
|
|
121
|
+
filters: { mutationKey: ["todos", "add"], status: "pending" },
|
|
122
|
+
select: (mutation) => ({
|
|
123
|
+
variables: mutation.state.variables,
|
|
124
|
+
submittedAt: mutation.state.submittedAt,
|
|
125
|
+
}),
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
{
|
|
129
|
+
pending.map((todo) => <li key={todo.submittedAt}>{todo.variables.title}</li>);
|
|
130
|
+
}
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
Use mutation metadata to keep concurrent optimistic rows distinct.
|
|
134
|
+
|
|
135
|
+
Source: TanStack/query:docs/framework/react/guides/optimistic-updates.md
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: core/coordinate-query-execution
|
|
3
|
+
description: >
|
|
4
|
+
Use this when coordinating enabled, skipToken, dependent queries, useQueries,
|
|
5
|
+
parallel queries, disabled queries, background fetching indicators, isFetching,
|
|
6
|
+
fetchStatus, and declarative refetch behavior.
|
|
7
|
+
type: core
|
|
8
|
+
library: TanStack Query
|
|
9
|
+
library_version: "5.101.0"
|
|
10
|
+
requires:
|
|
11
|
+
- core/fetch-and-observe-queries
|
|
12
|
+
- core/tune-defaults-freshness-retries-and-refetching
|
|
13
|
+
sources:
|
|
14
|
+
- TanStack/query:docs/framework/react/guides/dependent-queries.md
|
|
15
|
+
- TanStack/query:docs/framework/react/guides/parallel-queries.md
|
|
16
|
+
- TanStack/query:docs/framework/react/guides/disabling-queries.md
|
|
17
|
+
- TanStack/query:docs/framework/react/guides/background-fetching-indicators.md
|
|
18
|
+
- TanStack/query:docs/framework/react/reference/useQueries.md
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## Setup
|
|
22
|
+
|
|
23
|
+
```tsx
|
|
24
|
+
import { useQuery } from "@tanstack/react-query";
|
|
25
|
+
|
|
26
|
+
export function Projects(props: { userId?: string }) {
|
|
27
|
+
const projects = useQuery({
|
|
28
|
+
queryKey: ["projects", props.userId],
|
|
29
|
+
queryFn: async () => [{ id: "p1", userId: props.userId }],
|
|
30
|
+
enabled: Boolean(props.userId),
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
return <pre>{JSON.stringify(projects.data ?? [])}</pre>;
|
|
34
|
+
}
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Core Patterns
|
|
38
|
+
|
|
39
|
+
### Gate by dependency
|
|
40
|
+
|
|
41
|
+
```ts
|
|
42
|
+
import { useQuery } from "@tanstack/react-query";
|
|
43
|
+
|
|
44
|
+
export function useUserProjects(userId: string | undefined) {
|
|
45
|
+
return useQuery({
|
|
46
|
+
queryKey: ["projects", userId],
|
|
47
|
+
queryFn: async () => [{ id: "p1", userId }],
|
|
48
|
+
enabled: userId !== undefined,
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### Run dynamic parallel queries
|
|
54
|
+
|
|
55
|
+
```ts
|
|
56
|
+
import { useQueries } from "@tanstack/react-query";
|
|
57
|
+
|
|
58
|
+
export function useMessages(ids: Array<string>) {
|
|
59
|
+
return useQueries({
|
|
60
|
+
queries: ids.map((id) => ({
|
|
61
|
+
queryKey: ["message", id],
|
|
62
|
+
queryFn: async () => ({ id, text: "Hello" }),
|
|
63
|
+
})),
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### Show background refresh separately
|
|
69
|
+
|
|
70
|
+
```tsx
|
|
71
|
+
import { useIsFetching } from "@tanstack/react-query";
|
|
72
|
+
|
|
73
|
+
export function GlobalRefreshIndicator() {
|
|
74
|
+
const count = useIsFetching();
|
|
75
|
+
return count > 0 ? <p>Refreshing</p> : null;
|
|
76
|
+
}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## Common Mistakes
|
|
80
|
+
|
|
81
|
+
### HIGH Imperative disabled query
|
|
82
|
+
|
|
83
|
+
Wrong:
|
|
84
|
+
|
|
85
|
+
```ts
|
|
86
|
+
import { useQuery } from "@tanstack/react-query";
|
|
87
|
+
|
|
88
|
+
export function useSearch(term: string) {
|
|
89
|
+
return useQuery({
|
|
90
|
+
queryKey: ["search"],
|
|
91
|
+
queryFn: async () => [term],
|
|
92
|
+
enabled: false,
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
Correct:
|
|
98
|
+
|
|
99
|
+
```ts
|
|
100
|
+
import { useQuery } from "@tanstack/react-query";
|
|
101
|
+
|
|
102
|
+
export function useSearch(term: string) {
|
|
103
|
+
return useQuery({
|
|
104
|
+
queryKey: ["search", term],
|
|
105
|
+
queryFn: async () => [term],
|
|
106
|
+
enabled: term.length > 0,
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
Permanent disabling opts out of normal invalidation and dependency-driven cache behavior.
|
|
112
|
+
|
|
113
|
+
Source: TanStack/query:docs/framework/react/guides/disabling-queries.md
|
|
114
|
+
|
|
115
|
+
### HIGH Duplicate useQueries keys
|
|
116
|
+
|
|
117
|
+
Wrong:
|
|
118
|
+
|
|
119
|
+
```ts
|
|
120
|
+
import { useQueries } from "@tanstack/react-query";
|
|
121
|
+
|
|
122
|
+
export function useUsers(ids: Array<string>) {
|
|
123
|
+
return useQueries({
|
|
124
|
+
queries: ids.map((id) => ({
|
|
125
|
+
queryKey: ["user"],
|
|
126
|
+
queryFn: async () => ({ id }),
|
|
127
|
+
})),
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
Correct:
|
|
133
|
+
|
|
134
|
+
```ts
|
|
135
|
+
import { useQueries } from "@tanstack/react-query";
|
|
136
|
+
|
|
137
|
+
export function useUsers(ids: Array<string>) {
|
|
138
|
+
return useQueries({
|
|
139
|
+
queries: ids.map((id) => ({
|
|
140
|
+
queryKey: ["user", id],
|
|
141
|
+
queryFn: async () => ({ id }),
|
|
142
|
+
})),
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
Duplicate keys can share placeholder, selected, or cached data between different items.
|
|
148
|
+
|
|
149
|
+
Source: TanStack/query:docs/framework/react/reference/useQueries.md
|
|
150
|
+
|
|
151
|
+
### MEDIUM Full-page spinner on background refetch
|
|
152
|
+
|
|
153
|
+
Wrong:
|
|
154
|
+
|
|
155
|
+
```tsx
|
|
156
|
+
import { useQuery } from "@tanstack/react-query";
|
|
157
|
+
|
|
158
|
+
export function Todos() {
|
|
159
|
+
const query = useQuery({
|
|
160
|
+
queryKey: ["todos"],
|
|
161
|
+
queryFn: async () => [{ id: 1 }],
|
|
162
|
+
});
|
|
163
|
+
return query.isFetching ? <p>Loading</p> : <pre>{JSON.stringify(query.data)}</pre>;
|
|
164
|
+
}
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
Correct:
|
|
168
|
+
|
|
169
|
+
```tsx
|
|
170
|
+
import { useQuery } from "@tanstack/react-query";
|
|
171
|
+
|
|
172
|
+
export function Todos() {
|
|
173
|
+
const query = useQuery({
|
|
174
|
+
queryKey: ["todos"],
|
|
175
|
+
queryFn: async () => [{ id: 1 }],
|
|
176
|
+
});
|
|
177
|
+
return (
|
|
178
|
+
<pre>
|
|
179
|
+
{query.isFetching ? "Refreshing " : ""}
|
|
180
|
+
{JSON.stringify(query.data ?? [])}
|
|
181
|
+
</pre>
|
|
182
|
+
);
|
|
183
|
+
}
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
`isFetching` also covers background refreshes after data is already available.
|
|
187
|
+
|
|
188
|
+
Source: TanStack/query:docs/framework/react/guides/background-fetching-indicators.md
|
package/scaffold/.agents/skills/tanstack-query/references/core/design-query-keys-and-options.md
ADDED
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: core/design-query-keys-and-options
|
|
3
|
+
description: >
|
|
4
|
+
Use this when designing queryKey arrays, queryFn inputs, queryOptions,
|
|
5
|
+
infiniteQueryOptions, mutationOptions, skipToken, key factories, or TypeScript
|
|
6
|
+
inference for TanStack Query reads and writes.
|
|
7
|
+
type: core
|
|
8
|
+
library: TanStack Query
|
|
9
|
+
library_version: "5.101.0"
|
|
10
|
+
sources:
|
|
11
|
+
- TanStack/query:docs/framework/react/guides/query-keys.md
|
|
12
|
+
- TanStack/query:docs/framework/react/guides/query-options.md
|
|
13
|
+
- TanStack/query:docs/framework/react/typescript.md
|
|
14
|
+
- TanStack/query:docs/eslint/exhaustive-deps.md
|
|
15
|
+
- TanStack/query:docs/eslint/prefer-query-options.md
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## Setup
|
|
19
|
+
|
|
20
|
+
```ts
|
|
21
|
+
import { queryOptions } from "@tanstack/react-query";
|
|
22
|
+
|
|
23
|
+
export function todoOptions(todoId: string) {
|
|
24
|
+
return queryOptions({
|
|
25
|
+
queryKey: ["todo", todoId],
|
|
26
|
+
queryFn: async () => ({ id: todoId, title: "Ship skills" }),
|
|
27
|
+
staleTime: 60_000,
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Core Patterns
|
|
33
|
+
|
|
34
|
+
### Put every query variable in the key
|
|
35
|
+
|
|
36
|
+
```ts
|
|
37
|
+
import { queryOptions } from "@tanstack/react-query";
|
|
38
|
+
|
|
39
|
+
export function projectsOptions(teamId: string, page: number) {
|
|
40
|
+
return queryOptions({
|
|
41
|
+
queryKey: ["projects", teamId, page],
|
|
42
|
+
queryFn: async () => ({ teamId, page, items: [] as Array<{ id: string }> }),
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### Share one options factory
|
|
48
|
+
|
|
49
|
+
```ts
|
|
50
|
+
import { QueryClient, useQuery, queryOptions } from "@tanstack/react-query";
|
|
51
|
+
|
|
52
|
+
export const queryClient = new QueryClient();
|
|
53
|
+
|
|
54
|
+
export function userOptions(userId: string) {
|
|
55
|
+
return queryOptions({
|
|
56
|
+
queryKey: ["user", userId],
|
|
57
|
+
queryFn: async () => ({ id: userId, name: "Tanner" }),
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function useUser(userId: string) {
|
|
62
|
+
return useQuery(userOptions(userId));
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function preloadUser(userId: string) {
|
|
66
|
+
return queryClient.ensureQueryData(userOptions(userId));
|
|
67
|
+
}
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### Use skipToken for typesafe absence
|
|
71
|
+
|
|
72
|
+
```ts
|
|
73
|
+
import { skipToken, useQuery } from "@tanstack/react-query";
|
|
74
|
+
|
|
75
|
+
export function useMaybeTodo(todoId: string | undefined) {
|
|
76
|
+
return useQuery({
|
|
77
|
+
queryKey: ["todo", todoId],
|
|
78
|
+
queryFn: todoId ? async () => ({ id: todoId }) : skipToken,
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## Common Mistakes
|
|
84
|
+
|
|
85
|
+
### CRITICAL Missing variable in key
|
|
86
|
+
|
|
87
|
+
Wrong:
|
|
88
|
+
|
|
89
|
+
```ts
|
|
90
|
+
import { useQuery } from "@tanstack/react-query";
|
|
91
|
+
|
|
92
|
+
export function useProject(teamId: string) {
|
|
93
|
+
return useQuery({
|
|
94
|
+
queryKey: ["project"],
|
|
95
|
+
queryFn: async () => ({ teamId }),
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
Correct:
|
|
101
|
+
|
|
102
|
+
```ts
|
|
103
|
+
import { useQuery } from "@tanstack/react-query";
|
|
104
|
+
|
|
105
|
+
export function useProject(teamId: string) {
|
|
106
|
+
return useQuery({
|
|
107
|
+
queryKey: ["project", teamId],
|
|
108
|
+
queryFn: async () => ({ teamId }),
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
Query keys define cache identity; missing variables merge distinct data.
|
|
114
|
+
|
|
115
|
+
Source: TanStack/query:docs/framework/react/guides/query-keys.md
|
|
116
|
+
|
|
117
|
+
### HIGH Key and queryFn drift
|
|
118
|
+
|
|
119
|
+
Wrong:
|
|
120
|
+
|
|
121
|
+
```ts
|
|
122
|
+
import { useQuery } from "@tanstack/react-query";
|
|
123
|
+
|
|
124
|
+
export function useTodo(todoId: string) {
|
|
125
|
+
return useQuery({ queryKey: ["todo"], queryFn: async () => ({ id: todoId }) });
|
|
126
|
+
}
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
Correct:
|
|
130
|
+
|
|
131
|
+
```ts
|
|
132
|
+
import { queryOptions, useQuery } from "@tanstack/react-query";
|
|
133
|
+
|
|
134
|
+
function todoOptions(todoId: string) {
|
|
135
|
+
return queryOptions({
|
|
136
|
+
queryKey: ["todo", todoId],
|
|
137
|
+
queryFn: async () => ({ id: todoId }),
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export function useTodo(todoId: string) {
|
|
142
|
+
return useQuery(todoOptions(todoId));
|
|
143
|
+
}
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
Options factories keep identity, fetch behavior, and inference together across hooks and prefetches.
|
|
147
|
+
|
|
148
|
+
Source: TanStack/query:docs/eslint/prefer-query-options.md
|
|
149
|
+
|
|
150
|
+
### HIGH skipToken inside suspense query
|
|
151
|
+
|
|
152
|
+
Wrong:
|
|
153
|
+
|
|
154
|
+
```ts
|
|
155
|
+
import { skipToken, useSuspenseQuery } from "@tanstack/react-query";
|
|
156
|
+
|
|
157
|
+
export function useTodo(todoId: string | undefined) {
|
|
158
|
+
return useSuspenseQuery({
|
|
159
|
+
queryKey: ["todo", todoId],
|
|
160
|
+
queryFn: todoId ? async () => ({ id: todoId }) : skipToken,
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
Correct:
|
|
166
|
+
|
|
167
|
+
```ts
|
|
168
|
+
import { skipToken, useQuery } from "@tanstack/react-query";
|
|
169
|
+
|
|
170
|
+
export function useTodo(todoId: string | undefined) {
|
|
171
|
+
return useQuery({
|
|
172
|
+
queryKey: ["todo", todoId],
|
|
173
|
+
queryFn: todoId ? async () => ({ id: todoId }) : skipToken,
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
Suspense queries require a guaranteed query function and cannot be conditionally disabled.
|
|
179
|
+
|
|
180
|
+
Source: TanStack/query:docs/framework/react/guides/suspense.md
|
|
181
|
+
|
|
182
|
+
See also: `compositions/enforce-query-best-practices-with-eslint` for rules that enforce key and options mistakes.
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: core/fetch-and-observe-queries
|
|
3
|
+
description: >
|
|
4
|
+
Use this when reading server state with useQuery, useQueries, createQuery,
|
|
5
|
+
injectQuery, QueryObserver, QueryClient.fetchQuery, ensureQueryData, status,
|
|
6
|
+
fetchStatus, pending, paused, success, and error states.
|
|
7
|
+
type: core
|
|
8
|
+
library: TanStack Query
|
|
9
|
+
library_version: "5.101.0"
|
|
10
|
+
requires:
|
|
11
|
+
- lifecycle/setup-query-client-and-providers
|
|
12
|
+
- core/design-query-keys-and-options
|
|
13
|
+
sources:
|
|
14
|
+
- TanStack/query:docs/framework/react/guides/queries.md
|
|
15
|
+
- TanStack/query:docs/framework/react/guides/query-functions.md
|
|
16
|
+
- TanStack/query:docs/reference/QueryObserver.md
|
|
17
|
+
- TanStack/query:docs/reference/QueryClient.md
|
|
18
|
+
- TanStack/query:docs/eslint/no-void-query-fn.md
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## Setup
|
|
22
|
+
|
|
23
|
+
```tsx
|
|
24
|
+
import { useQuery } from "@tanstack/react-query";
|
|
25
|
+
|
|
26
|
+
export function Todos() {
|
|
27
|
+
const query = useQuery({
|
|
28
|
+
queryKey: ["todos"],
|
|
29
|
+
queryFn: async () => [{ id: 1, title: "Ship" }],
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
if (query.isPending) return <p>Loading</p>;
|
|
33
|
+
if (query.isError) return <p>{query.error.message}</p>;
|
|
34
|
+
return <pre>{JSON.stringify(query.data)}</pre>;
|
|
35
|
+
}
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Core Patterns
|
|
39
|
+
|
|
40
|
+
### Fetch imperatively through QueryClient
|
|
41
|
+
|
|
42
|
+
```ts
|
|
43
|
+
import { QueryClient } from "@tanstack/react-query";
|
|
44
|
+
|
|
45
|
+
const queryClient = new QueryClient();
|
|
46
|
+
|
|
47
|
+
export function loadTodo(todoId: string) {
|
|
48
|
+
return queryClient.fetchQuery({
|
|
49
|
+
queryKey: ["todo", todoId],
|
|
50
|
+
queryFn: async () => ({ id: todoId }),
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### Ensure cached data for loaders
|
|
56
|
+
|
|
57
|
+
```ts
|
|
58
|
+
import { QueryClient } from "@tanstack/react-query";
|
|
59
|
+
|
|
60
|
+
const queryClient = new QueryClient();
|
|
61
|
+
|
|
62
|
+
export function ensureTodos() {
|
|
63
|
+
return queryClient.ensureQueryData({
|
|
64
|
+
queryKey: ["todos"],
|
|
65
|
+
queryFn: async () => [{ id: 1 }],
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### Distinguish status from fetchStatus
|
|
71
|
+
|
|
72
|
+
```tsx
|
|
73
|
+
import { useQuery } from "@tanstack/react-query";
|
|
74
|
+
|
|
75
|
+
export function TodoCount() {
|
|
76
|
+
const query = useQuery({
|
|
77
|
+
queryKey: ["todos"],
|
|
78
|
+
queryFn: async () => [{ id: 1 }],
|
|
79
|
+
});
|
|
80
|
+
const label =
|
|
81
|
+
query.fetchStatus === "fetching" && query.status === "success" ? "Refreshing" : "Ready";
|
|
82
|
+
return (
|
|
83
|
+
<p>
|
|
84
|
+
{query.data?.length ?? 0} {label}
|
|
85
|
+
</p>
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## Common Mistakes
|
|
91
|
+
|
|
92
|
+
### CRITICAL Void query function
|
|
93
|
+
|
|
94
|
+
Wrong:
|
|
95
|
+
|
|
96
|
+
```ts
|
|
97
|
+
import { useQuery } from "@tanstack/react-query";
|
|
98
|
+
|
|
99
|
+
export function useTodos() {
|
|
100
|
+
return useQuery({
|
|
101
|
+
queryKey: ["todos"],
|
|
102
|
+
queryFn: async () => {
|
|
103
|
+
await Promise.resolve([{ id: 1 }]);
|
|
104
|
+
},
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
Correct:
|
|
110
|
+
|
|
111
|
+
```ts
|
|
112
|
+
import { useQuery } from "@tanstack/react-query";
|
|
113
|
+
|
|
114
|
+
export function useTodos() {
|
|
115
|
+
return useQuery({
|
|
116
|
+
queryKey: ["todos"],
|
|
117
|
+
queryFn: async () => Promise.resolve([{ id: 1 }]),
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
Query functions must return data; a missing return caches `undefined`.
|
|
123
|
+
|
|
124
|
+
Source: TanStack/query:docs/eslint/no-void-query-fn.md
|
|
125
|
+
|
|
126
|
+
### HIGH Only checking pending offline
|
|
127
|
+
|
|
128
|
+
Wrong:
|
|
129
|
+
|
|
130
|
+
```tsx
|
|
131
|
+
import { useQuery } from "@tanstack/react-query";
|
|
132
|
+
|
|
133
|
+
export function Todos() {
|
|
134
|
+
const query = useQuery({
|
|
135
|
+
queryKey: ["todos"],
|
|
136
|
+
queryFn: async () => [{ id: 1 }],
|
|
137
|
+
networkMode: "online",
|
|
138
|
+
});
|
|
139
|
+
return query.isPending ? <p>Loading</p> : <p>{query.fetchStatus}</p>;
|
|
140
|
+
}
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
Correct:
|
|
144
|
+
|
|
145
|
+
```tsx
|
|
146
|
+
import { useQuery } from "@tanstack/react-query";
|
|
147
|
+
|
|
148
|
+
export function Todos() {
|
|
149
|
+
const query = useQuery({
|
|
150
|
+
queryKey: ["todos"],
|
|
151
|
+
queryFn: async () => [{ id: 1 }],
|
|
152
|
+
networkMode: "online",
|
|
153
|
+
});
|
|
154
|
+
return query.fetchStatus === "paused" ? <p>Offline</p> : <p>{query.status}</p>;
|
|
155
|
+
}
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
`status` describes data state; `fetchStatus` describes whether fetching is paused, fetching, or idle.
|
|
159
|
+
|
|
160
|
+
Source: TanStack/query:docs/framework/react/guides/queries.md
|
|
161
|
+
|
|
162
|
+
### HIGH Using queries for writes
|
|
163
|
+
|
|
164
|
+
Wrong:
|
|
165
|
+
|
|
166
|
+
```ts
|
|
167
|
+
import { useQuery } from "@tanstack/react-query";
|
|
168
|
+
|
|
169
|
+
export function useSaveTodo(title: string) {
|
|
170
|
+
return useQuery({
|
|
171
|
+
queryKey: ["saveTodo", title],
|
|
172
|
+
queryFn: async () => ({ id: 1, title }),
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
Correct:
|
|
178
|
+
|
|
179
|
+
```ts
|
|
180
|
+
import { useMutation } from "@tanstack/react-query";
|
|
181
|
+
|
|
182
|
+
export function useSaveTodo() {
|
|
183
|
+
return useMutation({
|
|
184
|
+
mutationFn: async (title: string) => ({ id: 1, title }),
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
Queries are for reads; writes need mutation lifecycle hooks, invalidation, rollback, and mutation state.
|
|
190
|
+
|
|
191
|
+
Source: TanStack/query:docs/framework/react/guides/mutations.md
|
|
192
|
+
|
|
193
|
+
See also: `core/tune-defaults-freshness-retries-and-refetching` for how defaults change status behavior.
|