@hitch42/applet 0.1.1-beta.1 → 0.1.1-beta.11
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
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: compositions/persist-offline-and-restore-caches
|
|
3
|
+
description: >
|
|
4
|
+
Use this when using persistQueryClient, PersistQueryClientProvider,
|
|
5
|
+
createSyncStoragePersister, createAsyncStoragePersister, experimental
|
|
6
|
+
fine-grained persisters, offline mutations, resumePausedMutations, maxAge,
|
|
7
|
+
gcTime, and restore races.
|
|
8
|
+
type: composition
|
|
9
|
+
library: TanStack Query
|
|
10
|
+
library_version: "5.101.0"
|
|
11
|
+
requires:
|
|
12
|
+
- lifecycle/setup-query-client-and-providers
|
|
13
|
+
- core/tune-defaults-freshness-retries-and-refetching
|
|
14
|
+
- core/write-mutations-and-invalidate-related-queries
|
|
15
|
+
sources:
|
|
16
|
+
- TanStack/query:docs/framework/react/plugins/persistQueryClient.md
|
|
17
|
+
- TanStack/query:docs/framework/react/plugins/createSyncStoragePersister.md
|
|
18
|
+
- TanStack/query:docs/framework/react/plugins/createAsyncStoragePersister.md
|
|
19
|
+
- TanStack/query:docs/framework/react/plugins/createPersister.md
|
|
20
|
+
- TanStack/query:docs/framework/react/guides/network-mode.md
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## Setup
|
|
24
|
+
|
|
25
|
+
```tsx
|
|
26
|
+
import * as React from "react";
|
|
27
|
+
import { QueryClient } from "@tanstack/react-query";
|
|
28
|
+
import { PersistQueryClientProvider } from "@tanstack/react-query-persist-client";
|
|
29
|
+
import { createSyncStoragePersister } from "@tanstack/query-sync-storage-persister";
|
|
30
|
+
|
|
31
|
+
const queryClient = new QueryClient({
|
|
32
|
+
defaultOptions: { queries: { gcTime: 24 * 60 * 60 * 1000 } },
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
const persister = createSyncStoragePersister({
|
|
36
|
+
storage: window.localStorage,
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
export function AppProviders(props: { children: React.ReactNode }) {
|
|
40
|
+
return (
|
|
41
|
+
<PersistQueryClientProvider client={queryClient} persistOptions={{ persister }}>
|
|
42
|
+
{props.children}
|
|
43
|
+
</PersistQueryClientProvider>
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Core Integration Patterns
|
|
49
|
+
|
|
50
|
+
### Resume paused mutations after restore
|
|
51
|
+
|
|
52
|
+
```ts
|
|
53
|
+
import { QueryClient } from "@tanstack/react-query";
|
|
54
|
+
|
|
55
|
+
export const queryClient = new QueryClient();
|
|
56
|
+
|
|
57
|
+
queryClient.setMutationDefaults(["todos"], {
|
|
58
|
+
mutationFn: async (todo: { id: number; title: string }) => todo,
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
export function resumeMutations() {
|
|
62
|
+
return queryClient.resumePausedMutations();
|
|
63
|
+
}
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### Align cache lifetime with persistence
|
|
67
|
+
|
|
68
|
+
```ts
|
|
69
|
+
import { QueryClient } from "@tanstack/react-query";
|
|
70
|
+
|
|
71
|
+
export const queryClient = new QueryClient({
|
|
72
|
+
defaultOptions: {
|
|
73
|
+
queries: { gcTime: 7 * 24 * 60 * 60 * 1000 },
|
|
74
|
+
},
|
|
75
|
+
});
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### Use networkMode for offline-first writes
|
|
79
|
+
|
|
80
|
+
```ts
|
|
81
|
+
import { useMutation } from "@tanstack/react-query";
|
|
82
|
+
|
|
83
|
+
export function useSaveDraft() {
|
|
84
|
+
return useMutation({
|
|
85
|
+
mutationKey: ["saveDraft"],
|
|
86
|
+
mutationFn: async (draft: { body: string }) => draft,
|
|
87
|
+
networkMode: "offlineFirst",
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## Common Mistakes
|
|
93
|
+
|
|
94
|
+
### HIGH gcTime shorter than maxAge
|
|
95
|
+
|
|
96
|
+
Wrong:
|
|
97
|
+
|
|
98
|
+
```ts
|
|
99
|
+
import { QueryClient } from "@tanstack/react-query";
|
|
100
|
+
|
|
101
|
+
export const queryClient = new QueryClient({
|
|
102
|
+
defaultOptions: { queries: { gcTime: 5 * 60 * 1000 } },
|
|
103
|
+
});
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
Correct:
|
|
107
|
+
|
|
108
|
+
```ts
|
|
109
|
+
import { QueryClient } from "@tanstack/react-query";
|
|
110
|
+
|
|
111
|
+
export const queryClient = new QueryClient({
|
|
112
|
+
defaultOptions: { queries: { gcTime: 24 * 60 * 60 * 1000 } },
|
|
113
|
+
});
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
Persisted data can be garbage-collected before the persister maxAge can restore it.
|
|
117
|
+
|
|
118
|
+
Source: TanStack/query:docs/framework/react/plugins/persistQueryClient.md
|
|
119
|
+
|
|
120
|
+
### CRITICAL Rendering before restore
|
|
121
|
+
|
|
122
|
+
Wrong:
|
|
123
|
+
|
|
124
|
+
```tsx
|
|
125
|
+
import { QueryClientProvider, QueryClient } from "@tanstack/react-query";
|
|
126
|
+
|
|
127
|
+
const queryClient = new QueryClient();
|
|
128
|
+
export function App(props: { children: React.ReactNode }) {
|
|
129
|
+
return <QueryClientProvider client={queryClient}>{props.children}</QueryClientProvider>;
|
|
130
|
+
}
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
Correct:
|
|
134
|
+
|
|
135
|
+
```tsx
|
|
136
|
+
import { QueryClient } from "@tanstack/react-query";
|
|
137
|
+
import { PersistQueryClientProvider } from "@tanstack/react-query-persist-client";
|
|
138
|
+
import { createSyncStoragePersister } from "@tanstack/query-sync-storage-persister";
|
|
139
|
+
|
|
140
|
+
const queryClient = new QueryClient();
|
|
141
|
+
const persister = createSyncStoragePersister({ storage: window.localStorage });
|
|
142
|
+
|
|
143
|
+
export function App(props: { children: React.ReactNode }) {
|
|
144
|
+
return (
|
|
145
|
+
<PersistQueryClientProvider client={queryClient} persistOptions={{ persister }}>
|
|
146
|
+
{props.children}
|
|
147
|
+
</PersistQueryClientProvider>
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
The persistence provider prevents query fetching while restore is in progress.
|
|
153
|
+
|
|
154
|
+
Source: TanStack/query:docs/framework/react/plugins/persistQueryClient.md
|
|
155
|
+
|
|
156
|
+
### CRITICAL Missing default mutationFn
|
|
157
|
+
|
|
158
|
+
Wrong:
|
|
159
|
+
|
|
160
|
+
```ts
|
|
161
|
+
import { QueryClient } from "@tanstack/react-query";
|
|
162
|
+
|
|
163
|
+
export const queryClient = new QueryClient();
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
Correct:
|
|
167
|
+
|
|
168
|
+
```ts
|
|
169
|
+
import { QueryClient } from "@tanstack/react-query";
|
|
170
|
+
|
|
171
|
+
export const queryClient = new QueryClient();
|
|
172
|
+
queryClient.setMutationDefaults(["saveDraft"], {
|
|
173
|
+
mutationFn: async (draft: { body: string }) => draft,
|
|
174
|
+
});
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
Paused persisted mutations cannot resume without a serializable mutation key mapped to a default mutationFn.
|
|
178
|
+
|
|
179
|
+
Source: TanStack/query:docs/framework/react/plugins/persistQueryClient.md
|
|
180
|
+
|
|
181
|
+
See also: `core/tune-defaults-freshness-retries-and-refetching` for gcTime and networkMode.
|
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
|