@hitch42/applet 0.1.0-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/LICENSE +21 -0
- package/dist/_internal/client.d.ts +7 -0
- package/dist/_internal/client.js +169 -0
- package/dist/_internal/server.d.ts +92 -0
- package/dist/_internal/server.js +181 -0
- package/dist/applet.d.ts +1 -0
- package/dist/applet.js +184 -0
- package/dist/batch-Qa4B4-Yp.d.ts +30 -0
- package/dist/build-BlmLiKro.js +90 -0
- package/dist/build-CcT-wNN9.js +2 -0
- package/dist/ci-0Q2mUh53.js +68 -0
- package/dist/client.d.ts +48 -0
- package/dist/client.js +38 -0
- package/dist/contract-QUvEGiRI.d.ts +23 -0
- package/dist/deploy-BbL3GObV.js +2579 -0
- package/dist/email-DBTVhNB_.js +62 -0
- package/dist/generate-XB-72Fzj.js +537 -0
- package/dist/generate-wIN3IYqL.js +2 -0
- package/dist/index.d.ts +185 -0
- package/dist/index.js +251 -0
- package/dist/init-DwsXRUk-.js +244 -0
- package/dist/lazy-client-x0DCI98S.js +64 -0
- package/dist/load-config-OKZU3UKn.js +27 -0
- package/dist/login-BBO74hPC.js +12 -0
- package/dist/logs-XvK6Zs5Y.js +183 -0
- package/dist/manifest-4ZAc6HUk.js +191 -0
- package/dist/manifest-I-9s8R36.d.ts +88 -0
- package/dist/me-BW7Paary.js +32 -0
- package/dist/project-B0qNXnFu.js +536 -0
- package/dist/prompt-ChS-grYO.js +29 -0
- package/dist/query-cache-CXmAyLv3.js +82 -0
- package/dist/react.d.ts +37 -0
- package/dist/react.js +515 -0
- package/dist/schedules-BkWZsbSs.js +42 -0
- package/dist/server.d.ts +46 -0
- package/dist/server.js +104 -0
- package/dist/telephony-protocol-D-nf3Gca.d.ts +76 -0
- package/dist/telephony.d.ts +77 -0
- package/dist/telephony.js +607 -0
- package/dist/upload-DKZzz5kW.d.ts +112 -0
- package/dist/upload-DRbVhuDm.js +114 -0
- package/dist/variables-0tjnAeut.js +108 -0
- package/dist/vite.d.ts +63 -0
- package/dist/vite.js +816 -0
- package/dist/workflow-CTo2kZF1.js +51 -0
- package/dist/workflow-DurM6Fwx.d.ts +28 -0
- package/dist/workflow-engine-MogejNfF.js +117 -0
- package/dist/workspace-DruAZPmV.js +390 -0
- package/package.json +112 -0
- package/scaffold/.agents/skills/hitch-applets/SKILL.md +163 -0
- package/scaffold/.agents/skills/hitch-applets/references/client.md +73 -0
- package/scaffold/.agents/skills/hitch-applets/references/email-tracking.md +49 -0
- package/scaffold/.agents/skills/hitch-applets/references/files.md +109 -0
- package/scaffold/.agents/skills/hitch-applets/references/manifest.md +198 -0
- package/scaffold/.agents/skills/hitch-applets/references/markdown.md +37 -0
- package/scaffold/.agents/skills/hitch-applets/references/performance.md +74 -0
- package/scaffold/.agents/skills/hitch-applets/references/records.md +176 -0
- package/scaffold/.agents/skills/hitch-applets/references/schedules.md +91 -0
- package/scaffold/.agents/skills/hitch-applets/references/server.md +95 -0
- package/scaffold/.agents/skills/hitch-applets/references/workflows.md +81 -0
- 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 +38 -0
- package/scaffold/applet/components.json +25 -0
- package/scaffold/applet/src/client/components/ui/alert.tsx +73 -0
- package/scaffold/applet/src/client/components/ui/avatar.tsx +100 -0
- package/scaffold/applet/src/client/components/ui/badge.tsx +55 -0
- package/scaffold/applet/src/client/components/ui/button.tsx +90 -0
- package/scaffold/applet/src/client/components/ui/card.tsx +85 -0
- package/scaffold/applet/src/client/components/ui/checkbox.tsx +35 -0
- package/scaffold/applet/src/client/components/ui/dialog.tsx +164 -0
- package/scaffold/applet/src/client/components/ui/dropdown-menu.tsx +235 -0
- package/scaffold/applet/src/client/components/ui/input-group.tsx +144 -0
- package/scaffold/applet/src/client/components/ui/input.tsx +22 -0
- package/scaffold/applet/src/client/components/ui/label.tsx +29 -0
- package/scaffold/applet/src/client/components/ui/popover.tsx +74 -0
- package/scaffold/applet/src/client/components/ui/select.tsx +243 -0
- package/scaffold/applet/src/client/components/ui/separator.tsx +25 -0
- package/scaffold/applet/src/client/components/ui/sheet.tsx +177 -0
- package/scaffold/applet/src/client/components/ui/skeleton.tsx +13 -0
- package/scaffold/applet/src/client/components/ui/spinner.tsx +17 -0
- package/scaffold/applet/src/client/components/ui/switch.tsx +41 -0
- package/scaffold/applet/src/client/components/ui/table.tsx +114 -0
- package/scaffold/applet/src/client/components/ui/tabs.tsx +78 -0
- package/scaffold/applet/src/client/components/ui/textarea.tsx +23 -0
- package/scaffold/applet/src/client/lib/utils.ts +1 -0
- package/scaffold/applet/src/client/styles/globals.css +136 -0
package/scaffold/.agents/skills/tanstack-query/references/compositions/query-data-and-forms.md
ADDED
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: compositions/query-data-and-forms
|
|
3
|
+
description: >
|
|
4
|
+
Use this when integrating TanStack Query data and mutations with forms:
|
|
5
|
+
initial server state, editable client state, dirty fields, background updates,
|
|
6
|
+
submit prevention, invalidation, reset-after-save, validation errors, and
|
|
7
|
+
avoiding blind copies from Query cache into local form state.
|
|
8
|
+
type: composition
|
|
9
|
+
library: TanStack Query
|
|
10
|
+
library_version: "5.101.0"
|
|
11
|
+
requires:
|
|
12
|
+
- core/write-mutations-and-invalidate-related-queries
|
|
13
|
+
- core/selectors-and-derived-state
|
|
14
|
+
sources:
|
|
15
|
+
- TanStack/query:docs/framework/react/guides/mutations.md
|
|
16
|
+
- TanStack/query:docs/framework/react/guides/invalidations-from-mutations.md
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## Core Patterns
|
|
20
|
+
|
|
21
|
+
Treat form state as client state after the user starts editing. Server state can initialize the form, but it should not be copied blindly on every query update.
|
|
22
|
+
|
|
23
|
+
### Initial data only
|
|
24
|
+
|
|
25
|
+
Use this when one user owns the form and background updates are not useful while editing.
|
|
26
|
+
|
|
27
|
+
```tsx
|
|
28
|
+
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
|
29
|
+
|
|
30
|
+
function PersonDetail({ id }: { id: string }) {
|
|
31
|
+
const queryClient = useQueryClient();
|
|
32
|
+
const person = useQuery({
|
|
33
|
+
queryKey: ["person", id],
|
|
34
|
+
queryFn: () => fetchPerson(id),
|
|
35
|
+
staleTime: Infinity,
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
const updatePerson = useMutation({
|
|
39
|
+
mutationFn: savePerson,
|
|
40
|
+
onSuccess: () => queryClient.invalidateQueries({ queryKey: ["person", id] }),
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
if (!person.data) return <p>Loading...</p>;
|
|
44
|
+
return (
|
|
45
|
+
<PersonForm
|
|
46
|
+
person={person.data}
|
|
47
|
+
isSaving={updatePerson.isPending}
|
|
48
|
+
onSubmit={updatePerson.mutate}
|
|
49
|
+
/>
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### Derived server plus client state
|
|
55
|
+
|
|
56
|
+
Use this when background updates should remain visible for untouched fields.
|
|
57
|
+
|
|
58
|
+
```tsx
|
|
59
|
+
import * as React from "react";
|
|
60
|
+
import { useQuery } from "@tanstack/react-query";
|
|
61
|
+
|
|
62
|
+
function PersonName({ id }: { id: string }) {
|
|
63
|
+
const { data } = useQuery({
|
|
64
|
+
queryKey: ["person", id],
|
|
65
|
+
queryFn: () => fetchPerson(id),
|
|
66
|
+
});
|
|
67
|
+
const [draft, setDraft] = React.useState<{ firstName?: string }>({});
|
|
68
|
+
|
|
69
|
+
if (!data) return <p>Loading...</p>;
|
|
70
|
+
|
|
71
|
+
return (
|
|
72
|
+
<input
|
|
73
|
+
value={draft.firstName ?? data.firstName}
|
|
74
|
+
onChange={(event) => setDraft({ firstName: event.target.value })}
|
|
75
|
+
/>
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### Reset after awaited invalidation
|
|
81
|
+
|
|
82
|
+
```tsx
|
|
83
|
+
const mutation = useMutation({
|
|
84
|
+
mutationFn: savePerson,
|
|
85
|
+
onSuccess: () => queryClient.invalidateQueries({ queryKey: ["person", id] }),
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
function submit(values: PersonFormValues) {
|
|
89
|
+
mutation.mutate(values, { onSuccess: () => resetFormDraft() });
|
|
90
|
+
}
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Return the invalidation promise from `onSuccess` or `onSettled` when the saved server state must be back in the cache before the form resets.
|
|
94
|
+
|
|
95
|
+
## Common Mistakes
|
|
96
|
+
|
|
97
|
+
### HIGH Initializing form defaults before query data exists
|
|
98
|
+
|
|
99
|
+
Wrong:
|
|
100
|
+
|
|
101
|
+
```tsx
|
|
102
|
+
const { data } = useQuery({
|
|
103
|
+
queryKey: ["person", id],
|
|
104
|
+
queryFn: () => fetchPerson(id),
|
|
105
|
+
});
|
|
106
|
+
const [draft, setDraft] = React.useState(data);
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
Correct:
|
|
110
|
+
|
|
111
|
+
```tsx
|
|
112
|
+
const { data } = useQuery({
|
|
113
|
+
queryKey: ["person", id],
|
|
114
|
+
queryFn: () => fetchPerson(id),
|
|
115
|
+
});
|
|
116
|
+
if (!data) return <p>Loading...</p>;
|
|
117
|
+
return <PersonForm initialPerson={data} />;
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
The first render usually has no query data. Split the form boundary or derive field values from query data plus draft state.
|
|
121
|
+
|
|
122
|
+
### HIGH Background refetch overwrites or hides dirty client state
|
|
123
|
+
|
|
124
|
+
Wrong:
|
|
125
|
+
|
|
126
|
+
```tsx
|
|
127
|
+
React.useEffect(() => {
|
|
128
|
+
setDraft(personQuery.data);
|
|
129
|
+
}, [personQuery.data]);
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
Correct:
|
|
133
|
+
|
|
134
|
+
```tsx
|
|
135
|
+
const shownFirstName = draft.firstName ?? personQuery.data?.firstName ?? "";
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
Do not synchronize every server update into the draft. Derive displayed values or intentionally opt out of background updates for initial-only forms.
|
|
139
|
+
|
|
140
|
+
### MEDIUM Form submit can run twice
|
|
141
|
+
|
|
142
|
+
Wrong:
|
|
143
|
+
|
|
144
|
+
```tsx
|
|
145
|
+
<button type="submit">Save</button>
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
Correct:
|
|
149
|
+
|
|
150
|
+
```tsx
|
|
151
|
+
<button type="submit" disabled={mutation.isPending}>
|
|
152
|
+
Save
|
|
153
|
+
</button>
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
Use mutation state to block duplicate writes while a submission is in flight.
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: core/build-query-abstractions
|
|
3
|
+
description: >
|
|
4
|
+
Use this when creating TanStack Query abstractions: queryOptions factories,
|
|
5
|
+
feature-local key modules, custom hooks built on top of options factories,
|
|
6
|
+
TypeScript inference, avoiding wide UseQueryOptions wrappers, and sharing
|
|
7
|
+
query configuration across hooks, loaders, prefetches, and QueryClient calls.
|
|
8
|
+
type: core
|
|
9
|
+
library: TanStack Query
|
|
10
|
+
library_version: "5.101.0"
|
|
11
|
+
requires:
|
|
12
|
+
- core/design-query-keys-and-options
|
|
13
|
+
sources:
|
|
14
|
+
- TanStack/query:docs/framework/react/guides/query-options.md
|
|
15
|
+
- TanStack/query:docs/framework/react/typescript.md
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## Core Patterns
|
|
19
|
+
|
|
20
|
+
Prefer a `queryOptions` factory as the base abstraction. Hooks, loaders, prefetches, suspense queries, and `QueryClient` calls can all consume it.
|
|
21
|
+
|
|
22
|
+
```ts
|
|
23
|
+
import { queryOptions, useQuery, useSuspenseQuery } from "@tanstack/react-query";
|
|
24
|
+
|
|
25
|
+
export function invoiceOptions(id: number) {
|
|
26
|
+
return queryOptions({
|
|
27
|
+
queryKey: ["invoice", id],
|
|
28
|
+
queryFn: () => fetchInvoice(id),
|
|
29
|
+
staleTime: 60_000,
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function useInvoice(id: number) {
|
|
34
|
+
return useQuery(invoiceOptions(id));
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function useSuspenseInvoice(id: number) {
|
|
38
|
+
return useSuspenseQuery(invoiceOptions(id));
|
|
39
|
+
}
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Compose one-off options at the usage site:
|
|
43
|
+
|
|
44
|
+
```ts
|
|
45
|
+
const invoice = useQuery({
|
|
46
|
+
...invoiceOptions(id),
|
|
47
|
+
select: (data) => data.createdAt,
|
|
48
|
+
throwOnError: true,
|
|
49
|
+
});
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Common Mistakes
|
|
53
|
+
|
|
54
|
+
### HIGH Custom hook is the only abstraction
|
|
55
|
+
|
|
56
|
+
Wrong:
|
|
57
|
+
|
|
58
|
+
```ts
|
|
59
|
+
export function useInvoice(id: number) {
|
|
60
|
+
return useQuery({
|
|
61
|
+
queryKey: ["invoice", id],
|
|
62
|
+
queryFn: () => fetchInvoice(id),
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Correct:
|
|
68
|
+
|
|
69
|
+
```ts
|
|
70
|
+
export function invoiceOptions(id: number) {
|
|
71
|
+
return queryOptions({
|
|
72
|
+
queryKey: ["invoice", id],
|
|
73
|
+
queryFn: () => fetchInvoice(id),
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function useInvoice(id: number) {
|
|
78
|
+
return useQuery(invoiceOptions(id));
|
|
79
|
+
}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Custom hooks cannot run in route loaders, server prefetches, or event handlers. Options factories can.
|
|
83
|
+
|
|
84
|
+
### HIGH Wide UseQueryOptions wrapper breaks inference
|
|
85
|
+
|
|
86
|
+
Wrong:
|
|
87
|
+
|
|
88
|
+
```ts
|
|
89
|
+
function useInvoice(id: number, options?: Partial<UseQueryOptions<Invoice>>) {
|
|
90
|
+
return useQuery({
|
|
91
|
+
queryKey: ["invoice", id],
|
|
92
|
+
queryFn: () => fetchInvoice(id),
|
|
93
|
+
...options,
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Correct:
|
|
99
|
+
|
|
100
|
+
```ts
|
|
101
|
+
useQuery({
|
|
102
|
+
...invoiceOptions(id),
|
|
103
|
+
select: (invoice) => invoice.createdAt,
|
|
104
|
+
});
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
Let `queryOptions` and usage-site composition preserve `select` inference.
|
|
108
|
+
|
|
109
|
+
### MEDIUM Wrapper hides Query result state
|
|
110
|
+
|
|
111
|
+
Wrong:
|
|
112
|
+
|
|
113
|
+
```ts
|
|
114
|
+
export function useInvoice(id: number) {
|
|
115
|
+
const { data } = useQuery(invoiceOptions(id));
|
|
116
|
+
return data;
|
|
117
|
+
}
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
Correct:
|
|
121
|
+
|
|
122
|
+
```ts
|
|
123
|
+
export function useInvoice(id: number) {
|
|
124
|
+
return useQuery(invoiceOptions(id));
|
|
125
|
+
}
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
Keep the Query result surface available unless the abstraction owns every loading, error, and refetch behavior.
|
|
129
|
+
|
|
130
|
+
Source: TanStack/query:docs/framework/react/guides/queries.md
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: core/cancel-queries-and-consume-abort-signals
|
|
3
|
+
description: >
|
|
4
|
+
Use this when handling query cancellation, AbortSignal, cancelQueries,
|
|
5
|
+
CancelledError, rollback after consumed signals, fetch cancellation, axios
|
|
6
|
+
cancellation, and optimistic update overwrite prevention.
|
|
7
|
+
type: core
|
|
8
|
+
library: TanStack Query
|
|
9
|
+
library_version: "5.101.0"
|
|
10
|
+
requires:
|
|
11
|
+
- core/fetch-and-observe-queries
|
|
12
|
+
sources:
|
|
13
|
+
- TanStack/query:docs/framework/react/guides/query-cancellation.md
|
|
14
|
+
- TanStack/query:docs/reference/QueryClient.md
|
|
15
|
+
- TanStack/query:packages/query-core/src/retryer.ts
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## Setup
|
|
19
|
+
|
|
20
|
+
```ts
|
|
21
|
+
import { useQuery } from "@tanstack/react-query";
|
|
22
|
+
|
|
23
|
+
export function useTodo(todoId: string) {
|
|
24
|
+
return useQuery({
|
|
25
|
+
queryKey: ["todo", todoId],
|
|
26
|
+
queryFn: async ({ signal }) => {
|
|
27
|
+
const response = await fetch(`/api/todos/${todoId}`, { signal });
|
|
28
|
+
return response.json() as Promise<{ id: string; title: string }>;
|
|
29
|
+
},
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Core Patterns
|
|
35
|
+
|
|
36
|
+
### Cancel before optimistic writes
|
|
37
|
+
|
|
38
|
+
```ts
|
|
39
|
+
import { QueryClient } from "@tanstack/react-query";
|
|
40
|
+
|
|
41
|
+
const queryClient = new QueryClient();
|
|
42
|
+
|
|
43
|
+
export async function prepareTodoWrite() {
|
|
44
|
+
await queryClient.cancelQueries({ queryKey: ["todos"] });
|
|
45
|
+
}
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### Consume one signal across nested fetches
|
|
49
|
+
|
|
50
|
+
```ts
|
|
51
|
+
import { queryOptions } from "@tanstack/react-query";
|
|
52
|
+
|
|
53
|
+
export const todosWithDetailsOptions = queryOptions({
|
|
54
|
+
queryKey: ["todos-with-details"],
|
|
55
|
+
queryFn: async ({ signal }) => {
|
|
56
|
+
const todos = await fetch("/api/todos", { signal }).then(
|
|
57
|
+
(response) => response.json() as Promise<Array<{ id: string }>>,
|
|
58
|
+
);
|
|
59
|
+
return Promise.all(
|
|
60
|
+
todos.map((todo) =>
|
|
61
|
+
fetch(`/api/todos/${todo.id}`, { signal }).then((response) => response.json()),
|
|
62
|
+
),
|
|
63
|
+
);
|
|
64
|
+
},
|
|
65
|
+
});
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### Cancel by query key
|
|
69
|
+
|
|
70
|
+
```ts
|
|
71
|
+
import { QueryClient } from "@tanstack/react-query";
|
|
72
|
+
|
|
73
|
+
const queryClient = new QueryClient();
|
|
74
|
+
|
|
75
|
+
export function cancelTodos() {
|
|
76
|
+
return queryClient.cancelQueries({ queryKey: ["todos"] });
|
|
77
|
+
}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## Common Mistakes
|
|
81
|
+
|
|
82
|
+
### HIGH Ignoring AbortSignal
|
|
83
|
+
|
|
84
|
+
Wrong:
|
|
85
|
+
|
|
86
|
+
```ts
|
|
87
|
+
import { useQuery } from "@tanstack/react-query";
|
|
88
|
+
|
|
89
|
+
export function useTodo(id: string) {
|
|
90
|
+
return useQuery({
|
|
91
|
+
queryKey: ["todo", id],
|
|
92
|
+
queryFn: async () => fetch(`/api/todos/${id}`).then((r) => r.json()),
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
Correct:
|
|
98
|
+
|
|
99
|
+
```ts
|
|
100
|
+
import { useQuery } from "@tanstack/react-query";
|
|
101
|
+
|
|
102
|
+
export function useTodo(id: string) {
|
|
103
|
+
return useQuery({
|
|
104
|
+
queryKey: ["todo", id],
|
|
105
|
+
queryFn: async ({ signal }) => fetch(`/api/todos/${id}`, { signal }).then((r) => r.json()),
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
TanStack Query provides an AbortSignal; the request is only cancelled if the query function consumes it.
|
|
111
|
+
|
|
112
|
+
Source: TanStack/query:docs/framework/react/guides/query-cancellation.md
|
|
113
|
+
|
|
114
|
+
### HIGH Assuming unmount cancels
|
|
115
|
+
|
|
116
|
+
Wrong:
|
|
117
|
+
|
|
118
|
+
```ts
|
|
119
|
+
import { useQuery } from "@tanstack/react-query";
|
|
120
|
+
|
|
121
|
+
export function useReport() {
|
|
122
|
+
return useQuery({
|
|
123
|
+
queryKey: ["report"],
|
|
124
|
+
queryFn: async () => fetch("/api/report").then((r) => r.json()),
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
Correct:
|
|
130
|
+
|
|
131
|
+
```ts
|
|
132
|
+
import { useQuery } from "@tanstack/react-query";
|
|
133
|
+
|
|
134
|
+
export function useReport() {
|
|
135
|
+
return useQuery({
|
|
136
|
+
queryKey: ["report"],
|
|
137
|
+
queryFn: async ({ signal }) => fetch("/api/report", { signal }).then((r) => r.json()),
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
Unused queries can continue and populate cache unless the signal is consumed.
|
|
143
|
+
|
|
144
|
+
Source: TanStack/query:docs/framework/react/guides/query-cancellation.md
|
|
145
|
+
|
|
146
|
+
### HIGH Suspense cancellation expected
|
|
147
|
+
|
|
148
|
+
Wrong:
|
|
149
|
+
|
|
150
|
+
```ts
|
|
151
|
+
import { useSuspenseQuery } from "@tanstack/react-query";
|
|
152
|
+
|
|
153
|
+
export function useTodo(id: string) {
|
|
154
|
+
return useSuspenseQuery({
|
|
155
|
+
queryKey: ["todo", id],
|
|
156
|
+
queryFn: async ({ signal }) => fetch(`/api/todos/${id}`, { signal }).then((r) => r.json()),
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
Correct:
|
|
162
|
+
|
|
163
|
+
```ts
|
|
164
|
+
import { useQuery } from "@tanstack/react-query";
|
|
165
|
+
|
|
166
|
+
export function useTodo(id: string) {
|
|
167
|
+
return useQuery({
|
|
168
|
+
queryKey: ["todo", id],
|
|
169
|
+
queryFn: async ({ signal }) => fetch(`/api/todos/${id}`, { signal }).then((r) => r.json()),
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
Cancellation limitations apply to Suspense hooks; use non-suspense queries when cancellation behavior is required.
|
|
175
|
+
|
|
176
|
+
Source: TanStack/query:docs/framework/react/guides/query-cancellation.md
|
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
|