autoworkflow 3.1.4 → 3.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/commands/analyze.md +19 -0
- package/.claude/commands/audit.md +174 -11
- package/.claude/commands/build.md +39 -0
- package/.claude/commands/commit.md +25 -0
- package/.claude/commands/fix.md +23 -0
- package/.claude/commands/plan.md +18 -0
- package/.claude/commands/suggest.md +23 -0
- package/.claude/commands/verify.md +18 -0
- package/.claude/hooks/post-bash-router.sh +20 -0
- package/.claude/hooks/post-commit.sh +140 -0
- package/.claude/hooks/pre-edit.sh +129 -0
- package/.claude/hooks/session-check.sh +79 -0
- package/.claude/settings.json +40 -6
- package/.claude/settings.local.json +3 -1
- package/.claude/skills/actix.md +337 -0
- package/.claude/skills/alembic.md +504 -0
- package/.claude/skills/angular.md +237 -0
- package/.claude/skills/api-design.md +187 -0
- package/.claude/skills/aspnet-core.md +377 -0
- package/.claude/skills/astro.md +245 -0
- package/.claude/skills/auth-clerk.md +327 -0
- package/.claude/skills/auth-firebase.md +367 -0
- package/.claude/skills/auth-nextauth.md +359 -0
- package/.claude/skills/auth-supabase.md +368 -0
- package/.claude/skills/axum.md +386 -0
- package/.claude/skills/blazor.md +456 -0
- package/.claude/skills/chi.md +348 -0
- package/.claude/skills/code-review.md +133 -0
- package/.claude/skills/csharp.md +296 -0
- package/.claude/skills/css-modules.md +325 -0
- package/.claude/skills/cypress.md +343 -0
- package/.claude/skills/debugging.md +133 -0
- package/.claude/skills/diesel.md +392 -0
- package/.claude/skills/django.md +301 -0
- package/.claude/skills/docker.md +319 -0
- package/.claude/skills/doctrine.md +473 -0
- package/.claude/skills/documentation.md +182 -0
- package/.claude/skills/dotnet.md +409 -0
- package/.claude/skills/drizzle.md +293 -0
- package/.claude/skills/echo.md +321 -0
- package/.claude/skills/eloquent.md +256 -0
- package/.claude/skills/emotion.md +426 -0
- package/.claude/skills/entity-framework.md +370 -0
- package/.claude/skills/express.md +316 -0
- package/.claude/skills/fastapi.md +329 -0
- package/.claude/skills/fastify.md +299 -0
- package/.claude/skills/fiber.md +315 -0
- package/.claude/skills/flask.md +322 -0
- package/.claude/skills/gin.md +342 -0
- package/.claude/skills/git.md +116 -0
- package/.claude/skills/github-actions.md +353 -0
- package/.claude/skills/go.md +377 -0
- package/.claude/skills/gorm.md +409 -0
- package/.claude/skills/graphql.md +478 -0
- package/.claude/skills/hibernate.md +379 -0
- package/.claude/skills/hono.md +306 -0
- package/.claude/skills/java.md +400 -0
- package/.claude/skills/jest.md +313 -0
- package/.claude/skills/jpa.md +282 -0
- package/.claude/skills/kotlin.md +347 -0
- package/.claude/skills/kubernetes.md +363 -0
- package/.claude/skills/laravel.md +414 -0
- package/.claude/skills/mcp-browser.md +320 -0
- package/.claude/skills/mcp-database.md +219 -0
- package/.claude/skills/mcp-fetch.md +241 -0
- package/.claude/skills/mcp-filesystem.md +204 -0
- package/.claude/skills/mcp-github.md +217 -0
- package/.claude/skills/mcp-memory.md +240 -0
- package/.claude/skills/mcp-search.md +218 -0
- package/.claude/skills/mcp-slack.md +262 -0
- package/.claude/skills/micronaut.md +388 -0
- package/.claude/skills/mongodb.md +319 -0
- package/.claude/skills/mongoose.md +355 -0
- package/.claude/skills/mysql.md +281 -0
- package/.claude/skills/nestjs.md +335 -0
- package/.claude/skills/nextjs-app-router.md +260 -0
- package/.claude/skills/nextjs-pages.md +172 -0
- package/.claude/skills/nuxt.md +202 -0
- package/.claude/skills/openapi.md +489 -0
- package/.claude/skills/performance.md +199 -0
- package/.claude/skills/php.md +398 -0
- package/.claude/skills/playwright.md +371 -0
- package/.claude/skills/postgresql.md +257 -0
- package/.claude/skills/prisma.md +293 -0
- package/.claude/skills/pydantic.md +304 -0
- package/.claude/skills/pytest.md +313 -0
- package/.claude/skills/python.md +272 -0
- package/.claude/skills/quarkus.md +377 -0
- package/.claude/skills/react.md +230 -0
- package/.claude/skills/redis.md +391 -0
- package/.claude/skills/refactoring.md +143 -0
- package/.claude/skills/remix.md +246 -0
- package/.claude/skills/rest-api.md +490 -0
- package/.claude/skills/rocket.md +366 -0
- package/.claude/skills/rust.md +341 -0
- package/.claude/skills/sass.md +380 -0
- package/.claude/skills/sea-orm.md +382 -0
- package/.claude/skills/security.md +167 -0
- package/.claude/skills/sequelize.md +395 -0
- package/.claude/skills/spring-boot.md +416 -0
- package/.claude/skills/sqlalchemy.md +269 -0
- package/.claude/skills/sqlx-rust.md +408 -0
- package/.claude/skills/state-jotai.md +346 -0
- package/.claude/skills/state-mobx.md +353 -0
- package/.claude/skills/state-pinia.md +431 -0
- package/.claude/skills/state-redux.md +337 -0
- package/.claude/skills/state-tanstack-query.md +434 -0
- package/.claude/skills/state-zustand.md +340 -0
- package/.claude/skills/styled-components.md +403 -0
- package/.claude/skills/svelte.md +238 -0
- package/.claude/skills/sveltekit.md +207 -0
- package/.claude/skills/symfony.md +437 -0
- package/.claude/skills/tailwind.md +279 -0
- package/.claude/skills/terraform.md +394 -0
- package/.claude/skills/testing-library.md +371 -0
- package/.claude/skills/trpc.md +426 -0
- package/.claude/skills/typeorm.md +368 -0
- package/.claude/skills/vitest.md +330 -0
- package/.claude/skills/vue.md +202 -0
- package/.claude/skills/warp.md +365 -0
- package/README.md +135 -52
- package/package.json +1 -1
- package/system/triggers.md +152 -11
|
@@ -0,0 +1,434 @@
|
|
|
1
|
+
# TanStack Query Skill
|
|
2
|
+
|
|
3
|
+
## Setup
|
|
4
|
+
\`\`\`typescript
|
|
5
|
+
// app/providers.tsx
|
|
6
|
+
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
|
7
|
+
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
|
|
8
|
+
|
|
9
|
+
const queryClient = new QueryClient({
|
|
10
|
+
defaultOptions: {
|
|
11
|
+
queries: {
|
|
12
|
+
staleTime: 60 * 1000, // 1 minute
|
|
13
|
+
gcTime: 5 * 60 * 1000, // 5 minutes (formerly cacheTime)
|
|
14
|
+
retry: 3,
|
|
15
|
+
refetchOnWindowFocus: true,
|
|
16
|
+
refetchOnReconnect: true,
|
|
17
|
+
},
|
|
18
|
+
mutations: {
|
|
19
|
+
retry: 1,
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
export function Providers({ children }: { children: React.ReactNode }) {
|
|
25
|
+
return (
|
|
26
|
+
<QueryClientProvider client={queryClient}>
|
|
27
|
+
{children}
|
|
28
|
+
<ReactQueryDevtools initialIsOpen={false} />
|
|
29
|
+
</QueryClientProvider>
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
\`\`\`
|
|
33
|
+
|
|
34
|
+
## Basic Queries
|
|
35
|
+
\`\`\`typescript
|
|
36
|
+
import { useQuery, useQueryClient } from '@tanstack/react-query';
|
|
37
|
+
|
|
38
|
+
// Basic query
|
|
39
|
+
function useUsers() {
|
|
40
|
+
return useQuery({
|
|
41
|
+
queryKey: ['users'],
|
|
42
|
+
queryFn: async () => {
|
|
43
|
+
const response = await fetch('/api/users');
|
|
44
|
+
if (!response.ok) throw new Error('Failed to fetch users');
|
|
45
|
+
return response.json() as Promise<User[]>;
|
|
46
|
+
},
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Query with parameters
|
|
51
|
+
function useUser(userId: string) {
|
|
52
|
+
return useQuery({
|
|
53
|
+
queryKey: ['user', userId],
|
|
54
|
+
queryFn: async () => {
|
|
55
|
+
const response = await fetch(\`/api/users/\${userId}\`);
|
|
56
|
+
if (!response.ok) throw new Error('User not found');
|
|
57
|
+
return response.json() as Promise<User>;
|
|
58
|
+
},
|
|
59
|
+
enabled: !!userId, // Only run if userId exists
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Usage in component
|
|
64
|
+
function UserProfile({ userId }: { userId: string }) {
|
|
65
|
+
const { data: user, isLoading, isError, error, refetch } = useUser(userId);
|
|
66
|
+
|
|
67
|
+
if (isLoading) return <div>Loading...</div>;
|
|
68
|
+
if (isError) return <div>Error: {error.message}</div>;
|
|
69
|
+
|
|
70
|
+
return (
|
|
71
|
+
<div>
|
|
72
|
+
<h1>{user.name}</h1>
|
|
73
|
+
<button onClick={() => refetch()}>Refresh</button>
|
|
74
|
+
</div>
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
\`\`\`
|
|
78
|
+
|
|
79
|
+
## Query Options
|
|
80
|
+
\`\`\`typescript
|
|
81
|
+
function usePosts(category: string) {
|
|
82
|
+
return useQuery({
|
|
83
|
+
queryKey: ['posts', { category }],
|
|
84
|
+
queryFn: () => fetchPosts(category),
|
|
85
|
+
|
|
86
|
+
// Timing
|
|
87
|
+
staleTime: 5 * 60 * 1000, // Data fresh for 5 minutes
|
|
88
|
+
gcTime: 10 * 60 * 1000, // Keep in cache for 10 minutes
|
|
89
|
+
refetchInterval: 30 * 1000, // Poll every 30 seconds
|
|
90
|
+
refetchIntervalInBackground: false, // Don't poll when tab hidden
|
|
91
|
+
|
|
92
|
+
// Behavior
|
|
93
|
+
enabled: !!category, // Conditional fetching
|
|
94
|
+
retry: 3, // Retry failed requests
|
|
95
|
+
retryDelay: (attemptIndex) => Math.min(1000 * 2 ** attemptIndex, 30000),
|
|
96
|
+
|
|
97
|
+
// Data transformation
|
|
98
|
+
select: (data) => data.filter((post) => post.published),
|
|
99
|
+
|
|
100
|
+
// Placeholder while loading
|
|
101
|
+
placeholderData: (previousData) => previousData,
|
|
102
|
+
|
|
103
|
+
// Initial data from cache or static
|
|
104
|
+
initialData: () => queryClient.getQueryData(['posts', { category: 'all' }]),
|
|
105
|
+
initialDataUpdatedAt: () =>
|
|
106
|
+
queryClient.getQueryState(['posts', { category: 'all' }])?.dataUpdatedAt,
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
\`\`\`
|
|
110
|
+
|
|
111
|
+
## Mutations
|
|
112
|
+
\`\`\`typescript
|
|
113
|
+
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
|
114
|
+
|
|
115
|
+
function useCreateUser() {
|
|
116
|
+
const queryClient = useQueryClient();
|
|
117
|
+
|
|
118
|
+
return useMutation({
|
|
119
|
+
mutationFn: async (newUser: CreateUserData) => {
|
|
120
|
+
const response = await fetch('/api/users', {
|
|
121
|
+
method: 'POST',
|
|
122
|
+
headers: { 'Content-Type': 'application/json' },
|
|
123
|
+
body: JSON.stringify(newUser),
|
|
124
|
+
});
|
|
125
|
+
if (!response.ok) throw new Error('Failed to create user');
|
|
126
|
+
return response.json() as Promise<User>;
|
|
127
|
+
},
|
|
128
|
+
|
|
129
|
+
// Callbacks
|
|
130
|
+
onMutate: async (newUser) => {
|
|
131
|
+
// Optimistic update
|
|
132
|
+
await queryClient.cancelQueries({ queryKey: ['users'] });
|
|
133
|
+
const previousUsers = queryClient.getQueryData<User[]>(['users']);
|
|
134
|
+
|
|
135
|
+
queryClient.setQueryData<User[]>(['users'], (old) => [
|
|
136
|
+
...(old ?? []),
|
|
137
|
+
{ ...newUser, id: 'temp-id' } as User,
|
|
138
|
+
]);
|
|
139
|
+
|
|
140
|
+
return { previousUsers };
|
|
141
|
+
},
|
|
142
|
+
|
|
143
|
+
onError: (err, newUser, context) => {
|
|
144
|
+
// Rollback on error
|
|
145
|
+
queryClient.setQueryData(['users'], context?.previousUsers);
|
|
146
|
+
},
|
|
147
|
+
|
|
148
|
+
onSuccess: (data) => {
|
|
149
|
+
// Invalidate and refetch
|
|
150
|
+
queryClient.invalidateQueries({ queryKey: ['users'] });
|
|
151
|
+
},
|
|
152
|
+
|
|
153
|
+
onSettled: () => {
|
|
154
|
+
// Always runs (success or error)
|
|
155
|
+
queryClient.invalidateQueries({ queryKey: ['users'] });
|
|
156
|
+
},
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Usage
|
|
161
|
+
function CreateUserForm() {
|
|
162
|
+
const createUser = useCreateUser();
|
|
163
|
+
|
|
164
|
+
const handleSubmit = async (data: CreateUserData) => {
|
|
165
|
+
try {
|
|
166
|
+
await createUser.mutateAsync(data);
|
|
167
|
+
toast.success('User created!');
|
|
168
|
+
} catch (error) {
|
|
169
|
+
toast.error('Failed to create user');
|
|
170
|
+
}
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
return (
|
|
174
|
+
<form onSubmit={handleSubmit}>
|
|
175
|
+
{/* form fields */}
|
|
176
|
+
<button type="submit" disabled={createUser.isPending}>
|
|
177
|
+
{createUser.isPending ? 'Creating...' : 'Create User'}
|
|
178
|
+
</button>
|
|
179
|
+
</form>
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
\`\`\`
|
|
183
|
+
|
|
184
|
+
## Optimistic Updates
|
|
185
|
+
\`\`\`typescript
|
|
186
|
+
function useUpdateTodo() {
|
|
187
|
+
const queryClient = useQueryClient();
|
|
188
|
+
|
|
189
|
+
return useMutation({
|
|
190
|
+
mutationFn: async ({ id, data }: { id: string; data: Partial<Todo> }) => {
|
|
191
|
+
const response = await fetch(\`/api/todos/\${id}\`, {
|
|
192
|
+
method: 'PATCH',
|
|
193
|
+
body: JSON.stringify(data),
|
|
194
|
+
});
|
|
195
|
+
return response.json();
|
|
196
|
+
},
|
|
197
|
+
|
|
198
|
+
onMutate: async ({ id, data }) => {
|
|
199
|
+
await queryClient.cancelQueries({ queryKey: ['todos'] });
|
|
200
|
+
|
|
201
|
+
const previousTodos = queryClient.getQueryData<Todo[]>(['todos']);
|
|
202
|
+
|
|
203
|
+
queryClient.setQueryData<Todo[]>(['todos'], (old) =>
|
|
204
|
+
old?.map((todo) =>
|
|
205
|
+
todo.id === id ? { ...todo, ...data } : todo
|
|
206
|
+
)
|
|
207
|
+
);
|
|
208
|
+
|
|
209
|
+
return { previousTodos };
|
|
210
|
+
},
|
|
211
|
+
|
|
212
|
+
onError: (err, variables, context) => {
|
|
213
|
+
queryClient.setQueryData(['todos'], context?.previousTodos);
|
|
214
|
+
},
|
|
215
|
+
|
|
216
|
+
onSettled: () => {
|
|
217
|
+
queryClient.invalidateQueries({ queryKey: ['todos'] });
|
|
218
|
+
},
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// Toggle todo optimistically
|
|
223
|
+
function TodoItem({ todo }: { todo: Todo }) {
|
|
224
|
+
const updateTodo = useUpdateTodo();
|
|
225
|
+
|
|
226
|
+
return (
|
|
227
|
+
<div>
|
|
228
|
+
<input
|
|
229
|
+
type="checkbox"
|
|
230
|
+
checked={todo.completed}
|
|
231
|
+
onChange={() =>
|
|
232
|
+
updateTodo.mutate({
|
|
233
|
+
id: todo.id,
|
|
234
|
+
data: { completed: !todo.completed },
|
|
235
|
+
})
|
|
236
|
+
}
|
|
237
|
+
/>
|
|
238
|
+
<span>{todo.text}</span>
|
|
239
|
+
</div>
|
|
240
|
+
);
|
|
241
|
+
}
|
|
242
|
+
\`\`\`
|
|
243
|
+
|
|
244
|
+
## Infinite Queries (Pagination)
|
|
245
|
+
\`\`\`typescript
|
|
246
|
+
import { useInfiniteQuery } from '@tanstack/react-query';
|
|
247
|
+
|
|
248
|
+
interface PostsResponse {
|
|
249
|
+
posts: Post[];
|
|
250
|
+
nextCursor: string | null;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
function useInfinitePosts() {
|
|
254
|
+
return useInfiniteQuery({
|
|
255
|
+
queryKey: ['posts', 'infinite'],
|
|
256
|
+
queryFn: async ({ pageParam }) => {
|
|
257
|
+
const response = await fetch(\`/api/posts?cursor=\${pageParam ?? ''}\`);
|
|
258
|
+
return response.json() as Promise<PostsResponse>;
|
|
259
|
+
},
|
|
260
|
+
initialPageParam: null as string | null,
|
|
261
|
+
getNextPageParam: (lastPage) => lastPage.nextCursor,
|
|
262
|
+
getPreviousPageParam: (firstPage) => firstPage.prevCursor,
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
function PostFeed() {
|
|
267
|
+
const {
|
|
268
|
+
data,
|
|
269
|
+
fetchNextPage,
|
|
270
|
+
hasNextPage,
|
|
271
|
+
isFetchingNextPage,
|
|
272
|
+
isLoading,
|
|
273
|
+
} = useInfinitePosts();
|
|
274
|
+
|
|
275
|
+
if (isLoading) return <div>Loading...</div>;
|
|
276
|
+
|
|
277
|
+
return (
|
|
278
|
+
<div>
|
|
279
|
+
{data?.pages.map((page, i) => (
|
|
280
|
+
<div key={i}>
|
|
281
|
+
{page.posts.map((post) => (
|
|
282
|
+
<PostCard key={post.id} post={post} />
|
|
283
|
+
))}
|
|
284
|
+
</div>
|
|
285
|
+
))}
|
|
286
|
+
|
|
287
|
+
<button
|
|
288
|
+
onClick={() => fetchNextPage()}
|
|
289
|
+
disabled={!hasNextPage || isFetchingNextPage}
|
|
290
|
+
>
|
|
291
|
+
{isFetchingNextPage
|
|
292
|
+
? 'Loading more...'
|
|
293
|
+
: hasNextPage
|
|
294
|
+
? 'Load More'
|
|
295
|
+
: 'No more posts'}
|
|
296
|
+
</button>
|
|
297
|
+
</div>
|
|
298
|
+
);
|
|
299
|
+
}
|
|
300
|
+
\`\`\`
|
|
301
|
+
|
|
302
|
+
## Prefetching
|
|
303
|
+
\`\`\`typescript
|
|
304
|
+
// Prefetch on hover/focus
|
|
305
|
+
function UserLink({ userId }: { userId: string }) {
|
|
306
|
+
const queryClient = useQueryClient();
|
|
307
|
+
|
|
308
|
+
const prefetchUser = () => {
|
|
309
|
+
queryClient.prefetchQuery({
|
|
310
|
+
queryKey: ['user', userId],
|
|
311
|
+
queryFn: () => fetchUser(userId),
|
|
312
|
+
staleTime: 5 * 60 * 1000,
|
|
313
|
+
});
|
|
314
|
+
};
|
|
315
|
+
|
|
316
|
+
return (
|
|
317
|
+
<Link
|
|
318
|
+
to={\`/users/\${userId}\`}
|
|
319
|
+
onMouseEnter={prefetchUser}
|
|
320
|
+
onFocus={prefetchUser}
|
|
321
|
+
>
|
|
322
|
+
View User
|
|
323
|
+
</Link>
|
|
324
|
+
);
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// Prefetch in loader (React Router)
|
|
328
|
+
export const loader = async ({ params }) => {
|
|
329
|
+
await queryClient.prefetchQuery({
|
|
330
|
+
queryKey: ['user', params.userId],
|
|
331
|
+
queryFn: () => fetchUser(params.userId),
|
|
332
|
+
});
|
|
333
|
+
return null;
|
|
334
|
+
};
|
|
335
|
+
|
|
336
|
+
// Prefetch related data after main query
|
|
337
|
+
function useUserWithPosts(userId: string) {
|
|
338
|
+
const queryClient = useQueryClient();
|
|
339
|
+
|
|
340
|
+
const userQuery = useQuery({
|
|
341
|
+
queryKey: ['user', userId],
|
|
342
|
+
queryFn: () => fetchUser(userId),
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
// Prefetch posts when user loads
|
|
346
|
+
useEffect(() => {
|
|
347
|
+
if (userQuery.data) {
|
|
348
|
+
queryClient.prefetchQuery({
|
|
349
|
+
queryKey: ['user', userId, 'posts'],
|
|
350
|
+
queryFn: () => fetchUserPosts(userId),
|
|
351
|
+
});
|
|
352
|
+
}
|
|
353
|
+
}, [userQuery.data, userId, queryClient]);
|
|
354
|
+
|
|
355
|
+
return userQuery;
|
|
356
|
+
}
|
|
357
|
+
\`\`\`
|
|
358
|
+
|
|
359
|
+
## Parallel & Dependent Queries
|
|
360
|
+
\`\`\`typescript
|
|
361
|
+
import { useQueries } from '@tanstack/react-query';
|
|
362
|
+
|
|
363
|
+
// Parallel queries
|
|
364
|
+
function useMultipleUsers(userIds: string[]) {
|
|
365
|
+
return useQueries({
|
|
366
|
+
queries: userIds.map((id) => ({
|
|
367
|
+
queryKey: ['user', id],
|
|
368
|
+
queryFn: () => fetchUser(id),
|
|
369
|
+
})),
|
|
370
|
+
combine: (results) => ({
|
|
371
|
+
data: results.map((r) => r.data),
|
|
372
|
+
isLoading: results.some((r) => r.isLoading),
|
|
373
|
+
isError: results.some((r) => r.isError),
|
|
374
|
+
}),
|
|
375
|
+
});
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
// Dependent queries
|
|
379
|
+
function useUserAndPosts(userId: string) {
|
|
380
|
+
const userQuery = useQuery({
|
|
381
|
+
queryKey: ['user', userId],
|
|
382
|
+
queryFn: () => fetchUser(userId),
|
|
383
|
+
});
|
|
384
|
+
|
|
385
|
+
const postsQuery = useQuery({
|
|
386
|
+
queryKey: ['user', userId, 'posts'],
|
|
387
|
+
queryFn: () => fetchUserPosts(userId),
|
|
388
|
+
enabled: !!userQuery.data, // Only run after user loads
|
|
389
|
+
});
|
|
390
|
+
|
|
391
|
+
return { userQuery, postsQuery };
|
|
392
|
+
}
|
|
393
|
+
\`\`\`
|
|
394
|
+
|
|
395
|
+
## Cache Manipulation
|
|
396
|
+
\`\`\`typescript
|
|
397
|
+
const queryClient = useQueryClient();
|
|
398
|
+
|
|
399
|
+
// Get cached data
|
|
400
|
+
const users = queryClient.getQueryData<User[]>(['users']);
|
|
401
|
+
|
|
402
|
+
// Set cached data
|
|
403
|
+
queryClient.setQueryData<User[]>(['users'], (old) => [...(old ?? []), newUser]);
|
|
404
|
+
|
|
405
|
+
// Invalidate (mark stale, refetch if active)
|
|
406
|
+
queryClient.invalidateQueries({ queryKey: ['users'] });
|
|
407
|
+
queryClient.invalidateQueries({ queryKey: ['user', userId] });
|
|
408
|
+
queryClient.invalidateQueries({ queryKey: ['user'] }); // All user queries
|
|
409
|
+
|
|
410
|
+
// Remove from cache
|
|
411
|
+
queryClient.removeQueries({ queryKey: ['user', userId] });
|
|
412
|
+
|
|
413
|
+
// Cancel ongoing queries
|
|
414
|
+
await queryClient.cancelQueries({ queryKey: ['users'] });
|
|
415
|
+
|
|
416
|
+
// Refetch
|
|
417
|
+
queryClient.refetchQueries({ queryKey: ['users'] });
|
|
418
|
+
\`\`\`
|
|
419
|
+
|
|
420
|
+
## ❌ DON'T
|
|
421
|
+
- Use inline queryFn (extract to named function)
|
|
422
|
+
- Forget to handle loading/error states
|
|
423
|
+
- Use same query key for different data
|
|
424
|
+
- Call mutations in render (use callbacks)
|
|
425
|
+
- Forget enabled: false for conditional queries
|
|
426
|
+
|
|
427
|
+
## ✅ DO
|
|
428
|
+
- Use structured query keys (['entity', id, 'relation'])
|
|
429
|
+
- Set appropriate staleTime for your data
|
|
430
|
+
- Use optimistic updates for better UX
|
|
431
|
+
- Prefetch data on hover/route change
|
|
432
|
+
- Use select to transform/filter data
|
|
433
|
+
- Invalidate related queries after mutations
|
|
434
|
+
- Use useQueries for parallel requests
|