@qazuor/claude-code-config 0.1.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/LICENSE +21 -0
- package/README.md +1248 -0
- package/dist/bin.cjs +11886 -0
- package/dist/bin.cjs.map +1 -0
- package/dist/bin.d.cts +1 -0
- package/dist/bin.d.ts +1 -0
- package/dist/bin.js +11869 -0
- package/dist/bin.js.map +1 -0
- package/dist/index.cjs +3887 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +1325 -0
- package/dist/index.d.ts +1325 -0
- package/dist/index.js +3835 -0
- package/dist/index.js.map +1 -0
- package/package.json +86 -0
- package/templates/.log/notifications.log +1775 -0
- package/templates/agents/README.md +164 -0
- package/templates/agents/_registry.json +443 -0
- package/templates/agents/design/content-writer.md +353 -0
- package/templates/agents/design/ux-ui-designer.md +382 -0
- package/templates/agents/engineering/astro-engineer.md +293 -0
- package/templates/agents/engineering/db-drizzle-engineer.md +360 -0
- package/templates/agents/engineering/express-engineer.md +316 -0
- package/templates/agents/engineering/fastify-engineer.md +399 -0
- package/templates/agents/engineering/hono-engineer.md +263 -0
- package/templates/agents/engineering/mongoose-engineer.md +473 -0
- package/templates/agents/engineering/nestjs-engineer.md +429 -0
- package/templates/agents/engineering/nextjs-engineer.md +451 -0
- package/templates/agents/engineering/node-typescript-engineer.md +347 -0
- package/templates/agents/engineering/prisma-engineer.md +432 -0
- package/templates/agents/engineering/react-senior-dev.md +394 -0
- package/templates/agents/engineering/tanstack-start-engineer.md +447 -0
- package/templates/agents/engineering/tech-lead.md +269 -0
- package/templates/agents/product/product-functional.md +329 -0
- package/templates/agents/product/product-technical.md +578 -0
- package/templates/agents/quality/debugger.md +514 -0
- package/templates/agents/quality/qa-engineer.md +390 -0
- package/templates/agents/specialized/enrichment-agent.md +277 -0
- package/templates/agents/specialized/i18n-specialist.md +322 -0
- package/templates/agents/specialized/seo-ai-specialist.md +387 -0
- package/templates/agents/specialized/tech-writer.md +300 -0
- package/templates/code-style/.editorconfig +27 -0
- package/templates/code-style/.prettierignore +25 -0
- package/templates/code-style/.prettierrc +12 -0
- package/templates/code-style/biome.json +78 -0
- package/templates/code-style/commitlint.config.js +44 -0
- package/templates/commands/README.md +175 -0
- package/templates/commands/_registry.json +420 -0
- package/templates/commands/add-new-entity.md +211 -0
- package/templates/commands/audit/accessibility-audit.md +360 -0
- package/templates/commands/audit/performance-audit.md +290 -0
- package/templates/commands/audit/security-audit.md +231 -0
- package/templates/commands/code-check.md +127 -0
- package/templates/commands/five-why.md +225 -0
- package/templates/commands/formatting/format-markdown.md +197 -0
- package/templates/commands/git/commit.md +247 -0
- package/templates/commands/meta/create-agent.md +257 -0
- package/templates/commands/meta/create-command.md +312 -0
- package/templates/commands/meta/create-skill.md +321 -0
- package/templates/commands/meta/help.md +318 -0
- package/templates/commands/planning/check-completed-tasks.md +224 -0
- package/templates/commands/planning/cleanup-issues.md +248 -0
- package/templates/commands/planning/planning-cleanup.md +251 -0
- package/templates/commands/planning/sync-planning-github.md +133 -0
- package/templates/commands/planning/sync-todos-github.md +203 -0
- package/templates/commands/quality-check.md +211 -0
- package/templates/commands/run-tests.md +159 -0
- package/templates/commands/start-feature-plan.md +232 -0
- package/templates/commands/start-refactor-plan.md +244 -0
- package/templates/commands/sync-planning.md +176 -0
- package/templates/commands/update-docs.md +242 -0
- package/templates/docs/CHECKPOINT-SYSTEM.md +504 -0
- package/templates/docs/INDEX.md +677 -0
- package/templates/docs/RECOMMENDED-HOOKS.md +415 -0
- package/templates/docs/_registry.json +329 -0
- package/templates/docs/diagrams/README.md +220 -0
- package/templates/docs/diagrams/agent-hierarchy.mmd +55 -0
- package/templates/docs/diagrams/documentation-map.mmd +61 -0
- package/templates/docs/diagrams/tools-relationship.mmd +55 -0
- package/templates/docs/diagrams/workflow-decision-tree.mmd +38 -0
- package/templates/docs/doc-sync.md +533 -0
- package/templates/docs/examples/end-to-end-workflow.md +1505 -0
- package/templates/docs/glossary.md +495 -0
- package/templates/docs/guides/mockup-prompt-engineering.md +644 -0
- package/templates/docs/guides/mockup-setup.md +737 -0
- package/templates/docs/learnings/README.md +250 -0
- package/templates/docs/learnings/common-architectural-patterns.md +123 -0
- package/templates/docs/learnings/common-mistakes-to-avoid.md +149 -0
- package/templates/docs/learnings/markdown-formatting-standards.md +104 -0
- package/templates/docs/learnings/monorepo-command-execution.md +64 -0
- package/templates/docs/learnings/optimization-tips.md +146 -0
- package/templates/docs/learnings/planning-linear-sync-workflow.md +70 -0
- package/templates/docs/learnings/shell-compatibility-fish.md +46 -0
- package/templates/docs/learnings/test-organization-structure.md +68 -0
- package/templates/docs/mcp-installation.md +613 -0
- package/templates/docs/mcp-servers.md +989 -0
- package/templates/docs/notification-installation.md +570 -0
- package/templates/docs/quick-start.md +354 -0
- package/templates/docs/standards/architecture-patterns.md +1064 -0
- package/templates/docs/standards/atomic-commits.md +513 -0
- package/templates/docs/standards/code-standards.md +993 -0
- package/templates/docs/standards/design-standards.md +656 -0
- package/templates/docs/standards/documentation-standards.md +1160 -0
- package/templates/docs/standards/testing-standards.md +969 -0
- package/templates/docs/system-maintenance.md +604 -0
- package/templates/docs/templates/PDR-template.md +561 -0
- package/templates/docs/templates/TODOs-template.md +534 -0
- package/templates/docs/templates/tech-analysis-template.md +800 -0
- package/templates/docs/workflows/README.md +519 -0
- package/templates/docs/workflows/atomic-task-protocol.md +955 -0
- package/templates/docs/workflows/decision-tree.md +482 -0
- package/templates/docs/workflows/edge-cases.md +856 -0
- package/templates/docs/workflows/phase-1-planning.md +957 -0
- package/templates/docs/workflows/phase-2-implementation.md +896 -0
- package/templates/docs/workflows/phase-3-validation.md +792 -0
- package/templates/docs/workflows/phase-4-finalization.md +927 -0
- package/templates/docs/workflows/quick-fix-protocol.md +505 -0
- package/templates/docs/workflows/task-atomization.md +537 -0
- package/templates/docs/workflows/task-completion-protocol.md +448 -0
- package/templates/hooks/on-notification.sh +28 -0
- package/templates/schemas/checkpoint.schema.json +97 -0
- package/templates/schemas/code-registry.schema.json +84 -0
- package/templates/schemas/pdr.schema.json +314 -0
- package/templates/schemas/problems.schema.json +55 -0
- package/templates/schemas/tech-analysis.schema.json +404 -0
- package/templates/schemas/telemetry.schema.json +298 -0
- package/templates/schemas/todos.schema.json +234 -0
- package/templates/schemas/workflows.schema.json +69 -0
- package/templates/scripts/add-changelogs.sh +105 -0
- package/templates/scripts/generate-code-registry.ts +270 -0
- package/templates/scripts/health-check.sh +343 -0
- package/templates/scripts/sync-registry.sh +40 -0
- package/templates/scripts/telemetry-report.ts +36 -0
- package/templates/scripts/validate-docs.sh +224 -0
- package/templates/scripts/validate-registry.sh +225 -0
- package/templates/scripts/validate-schemas.ts +283 -0
- package/templates/scripts/validate-structure.sh +165 -0
- package/templates/scripts/worktree-cleanup.sh +81 -0
- package/templates/scripts/worktree-create.sh +63 -0
- package/templates/sessions/planning/.gitkeep +0 -0
- package/templates/sessions/planning/archived/.gitkeep +0 -0
- package/templates/settings.json +202 -0
- package/templates/settings.local.json +138 -0
- package/templates/skills/README.md +197 -0
- package/templates/skills/_registry.json +473 -0
- package/templates/skills/audit/accessibility-audit.md +309 -0
- package/templates/skills/audit/performance-audit.md +257 -0
- package/templates/skills/audit/security-audit.md +217 -0
- package/templates/skills/auth/nextauth-patterns.md +308 -0
- package/templates/skills/brand-guidelines.md +240 -0
- package/templates/skills/documentation/markdown-formatter.md +302 -0
- package/templates/skills/git/git-commit-helper.md +321 -0
- package/templates/skills/i18n/i18n-patterns.md +251 -0
- package/templates/skills/patterns/error-handling-patterns.md +242 -0
- package/templates/skills/patterns/tdd-methodology.md +342 -0
- package/templates/skills/qa/qa-criteria-validator.md +383 -0
- package/templates/skills/qa/web-app-testing.md +398 -0
- package/templates/skills/react/react-hook-form-patterns.md +359 -0
- package/templates/skills/state/redux-toolkit-patterns.md +272 -0
- package/templates/skills/state/tanstack-query-patterns.md +299 -0
- package/templates/skills/state/zustand-patterns.md +301 -0
- package/templates/skills/tech/mermaid-diagram-specialist.md +195 -0
- package/templates/skills/tech/shadcn-specialist.md +252 -0
- package/templates/skills/tech/vercel-specialist.md +297 -0
- package/templates/skills/testing/api-app-testing.md +254 -0
- package/templates/skills/testing/performance-testing.md +275 -0
- package/templates/skills/testing/security-testing.md +348 -0
- package/templates/skills/utils/add-memory.md +295 -0
- package/templates/skills/utils/json-data-auditor.md +283 -0
- package/templates/skills/utils/pdf-creator-editor.md +342 -0
- package/templates/tools/format-markdown.sh +185 -0
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: tanstack-query-patterns
|
|
3
|
+
category: state
|
|
4
|
+
description: TanStack Query (React Query) server state management patterns
|
|
5
|
+
usage: Use when managing server state with automatic caching, refetching, and optimistic updates
|
|
6
|
+
input: API endpoints, query keys, mutation operations
|
|
7
|
+
output: Query hooks, mutation hooks, cache management
|
|
8
|
+
config_required:
|
|
9
|
+
stale_time: "Time until data is considered stale"
|
|
10
|
+
cache_time: "Time until inactive data is garbage collected"
|
|
11
|
+
retry_config: "Retry strategy for failed requests"
|
|
12
|
+
refetch_on_window_focus: "Whether to refetch on window focus"
|
|
13
|
+
api_base_url: "Base URL for API requests"
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
# TanStack Query Patterns
|
|
17
|
+
|
|
18
|
+
## ⚙️ Configuration
|
|
19
|
+
|
|
20
|
+
| Setting | Description | Example |
|
|
21
|
+
|---------|-------------|---------|
|
|
22
|
+
| `stale_time` | Time until data is stale | `5 * 60 * 1000` (5 minutes) |
|
|
23
|
+
| `cache_time` | Time until inactive data GC | `30 * 60 * 1000` (30 minutes) |
|
|
24
|
+
| `retry_config` | Failed request retry strategy | `3`, exponential backoff |
|
|
25
|
+
| `refetch_on_window_focus` | Refetch when window regains focus | `true`, `false` |
|
|
26
|
+
| `api_base_url` | API base URL | `/api`, `https://api.example.com` |
|
|
27
|
+
|
|
28
|
+
## Purpose
|
|
29
|
+
|
|
30
|
+
Manage server state with:
|
|
31
|
+
- Automatic caching and background refetching
|
|
32
|
+
- Stale-while-revalidate pattern
|
|
33
|
+
- Optimistic updates
|
|
34
|
+
- Infinite queries and pagination
|
|
35
|
+
- Minimal boilerplate
|
|
36
|
+
|
|
37
|
+
## Setup
|
|
38
|
+
|
|
39
|
+
```typescript
|
|
40
|
+
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
|
41
|
+
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
|
|
42
|
+
|
|
43
|
+
const queryClient = new QueryClient({
|
|
44
|
+
defaultOptions: {
|
|
45
|
+
queries: {
|
|
46
|
+
staleTime: 1000 * 60 * 5, // 5 minutes
|
|
47
|
+
gcTime: 1000 * 60 * 30, // 30 minutes
|
|
48
|
+
retry: 3,
|
|
49
|
+
retryDelay: (attemptIndex) => Math.min(1000 * 2 ** attemptIndex, 30000),
|
|
50
|
+
refetchOnWindowFocus: true,
|
|
51
|
+
},
|
|
52
|
+
mutations: {
|
|
53
|
+
retry: 1,
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
function App() {
|
|
59
|
+
return (
|
|
60
|
+
<QueryClientProvider client={queryClient}>
|
|
61
|
+
<Router />
|
|
62
|
+
<ReactQueryDevtools initialIsOpen={false} />
|
|
63
|
+
</QueryClientProvider>
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Query Patterns
|
|
69
|
+
|
|
70
|
+
### Basic Query
|
|
71
|
+
|
|
72
|
+
```typescript
|
|
73
|
+
import { useQuery } from '@tanstack/react-query';
|
|
74
|
+
|
|
75
|
+
async function fetchUser(userId: string): Promise<User> {
|
|
76
|
+
const response = await fetch(`/api/users/${userId}`);
|
|
77
|
+
if (!response.ok) throw new Error('Failed to fetch user');
|
|
78
|
+
return response.json();
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function useUser(userId: string) {
|
|
82
|
+
return useQuery({
|
|
83
|
+
queryKey: ['user', userId],
|
|
84
|
+
queryFn: () => fetchUser(userId),
|
|
85
|
+
enabled: !!userId,
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Usage
|
|
90
|
+
function UserProfile({ userId }: { userId: string }) {
|
|
91
|
+
const { data: user, isLoading, error, refetch } = useUser(userId);
|
|
92
|
+
|
|
93
|
+
if (isLoading) return <Spinner />;
|
|
94
|
+
if (error) return <Error message={error.message} />;
|
|
95
|
+
|
|
96
|
+
return <div>{user?.name}</div>;
|
|
97
|
+
}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### Infinite Query
|
|
101
|
+
|
|
102
|
+
```typescript
|
|
103
|
+
import { useInfiniteQuery } from '@tanstack/react-query';
|
|
104
|
+
|
|
105
|
+
interface PostsPage {
|
|
106
|
+
posts: Post[];
|
|
107
|
+
nextCursor: string | null;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function useInfinitePosts() {
|
|
111
|
+
return useInfiniteQuery({
|
|
112
|
+
queryKey: ['posts'],
|
|
113
|
+
queryFn: ({ pageParam }) => fetchPosts(pageParam),
|
|
114
|
+
initialPageParam: undefined as string | undefined,
|
|
115
|
+
getNextPageParam: (lastPage) => lastPage.nextCursor ?? undefined,
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Usage
|
|
120
|
+
function PostList() {
|
|
121
|
+
const { data, fetchNextPage, hasNextPage, isFetchingNextPage } = useInfinitePosts();
|
|
122
|
+
|
|
123
|
+
return (
|
|
124
|
+
<div>
|
|
125
|
+
{data?.pages.map((page, i) => (
|
|
126
|
+
<Fragment key={i}>
|
|
127
|
+
{page.posts.map((post) => <PostCard key={post.id} post={post} />)}
|
|
128
|
+
</Fragment>
|
|
129
|
+
))}
|
|
130
|
+
{hasNextPage && (
|
|
131
|
+
<button onClick={() => fetchNextPage()} disabled={isFetchingNextPage}>
|
|
132
|
+
{isFetchingNextPage ? 'Loading...' : 'Load More'}
|
|
133
|
+
</button>
|
|
134
|
+
)}
|
|
135
|
+
</div>
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
## Mutation Patterns
|
|
141
|
+
|
|
142
|
+
### Basic Mutation
|
|
143
|
+
|
|
144
|
+
```typescript
|
|
145
|
+
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
|
146
|
+
|
|
147
|
+
function useCreatePost() {
|
|
148
|
+
const queryClient = useQueryClient();
|
|
149
|
+
|
|
150
|
+
return useMutation({
|
|
151
|
+
mutationFn: createPost,
|
|
152
|
+
onSuccess: (newPost) => {
|
|
153
|
+
// Invalidate and refetch
|
|
154
|
+
queryClient.invalidateQueries({ queryKey: ['posts'] });
|
|
155
|
+
|
|
156
|
+
// Or update cache directly
|
|
157
|
+
queryClient.setQueryData(['post', newPost.id], newPost);
|
|
158
|
+
},
|
|
159
|
+
onError: (error) => {
|
|
160
|
+
toast.error(error.message);
|
|
161
|
+
},
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Usage
|
|
166
|
+
function CreatePostForm() {
|
|
167
|
+
const createPost = useCreatePost();
|
|
168
|
+
|
|
169
|
+
const handleSubmit = async (data: CreatePostInput) => {
|
|
170
|
+
try {
|
|
171
|
+
await createPost.mutateAsync(data);
|
|
172
|
+
toast.success('Post created!');
|
|
173
|
+
} catch (error) {
|
|
174
|
+
// Error handled in mutation
|
|
175
|
+
}
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
return (
|
|
179
|
+
<form onSubmit={handleSubmit}>
|
|
180
|
+
<button type="submit" disabled={createPost.isPending}>
|
|
181
|
+
{createPost.isPending ? 'Creating...' : 'Create Post'}
|
|
182
|
+
</button>
|
|
183
|
+
</form>
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
### Optimistic Updates
|
|
189
|
+
|
|
190
|
+
```typescript
|
|
191
|
+
function useUpdatePost() {
|
|
192
|
+
const queryClient = useQueryClient();
|
|
193
|
+
|
|
194
|
+
return useMutation({
|
|
195
|
+
mutationFn: ({ id, data }: { id: string; data: Partial<Post> }) =>
|
|
196
|
+
updatePost(id, data),
|
|
197
|
+
|
|
198
|
+
onMutate: async ({ id, data }) => {
|
|
199
|
+
// Cancel outgoing refetches
|
|
200
|
+
await queryClient.cancelQueries({ queryKey: ['post', id] });
|
|
201
|
+
|
|
202
|
+
// Snapshot previous value
|
|
203
|
+
const previousPost = queryClient.getQueryData<Post>(['post', id]);
|
|
204
|
+
|
|
205
|
+
// Optimistically update
|
|
206
|
+
if (previousPost) {
|
|
207
|
+
queryClient.setQueryData<Post>(['post', id], {
|
|
208
|
+
...previousPost,
|
|
209
|
+
...data,
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
return { previousPost };
|
|
214
|
+
},
|
|
215
|
+
|
|
216
|
+
onError: (err, variables, context) => {
|
|
217
|
+
// Rollback on error
|
|
218
|
+
if (context?.previousPost) {
|
|
219
|
+
queryClient.setQueryData(['post', variables.id], context.previousPost);
|
|
220
|
+
}
|
|
221
|
+
},
|
|
222
|
+
|
|
223
|
+
onSettled: (data, error, variables) => {
|
|
224
|
+
// Always refetch after error or success
|
|
225
|
+
queryClient.invalidateQueries({ queryKey: ['post', variables.id] });
|
|
226
|
+
},
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
## Query Key Patterns
|
|
232
|
+
|
|
233
|
+
```typescript
|
|
234
|
+
export const queryKeys = {
|
|
235
|
+
all: ['posts'] as const,
|
|
236
|
+
lists: () => [...queryKeys.all, 'list'] as const,
|
|
237
|
+
list: (filters: PostFilters) => [...queryKeys.lists(), filters] as const,
|
|
238
|
+
details: () => [...queryKeys.all, 'detail'] as const,
|
|
239
|
+
detail: (id: string) => [...queryKeys.details(), id] as const,
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
// Usage
|
|
243
|
+
useQuery({
|
|
244
|
+
queryKey: queryKeys.detail(postId),
|
|
245
|
+
queryFn: () => fetchPost(postId),
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
// Invalidate all posts
|
|
249
|
+
queryClient.invalidateQueries({ queryKey: queryKeys.all });
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
## Prefetching
|
|
253
|
+
|
|
254
|
+
```typescript
|
|
255
|
+
// Prefetch on hover
|
|
256
|
+
function PostLink({ postId }: { postId: string }) {
|
|
257
|
+
const queryClient = useQueryClient();
|
|
258
|
+
|
|
259
|
+
const prefetch = () => {
|
|
260
|
+
queryClient.prefetchQuery({
|
|
261
|
+
queryKey: ['post', postId],
|
|
262
|
+
queryFn: () => fetchPost(postId),
|
|
263
|
+
staleTime: 1000 * 60,
|
|
264
|
+
});
|
|
265
|
+
};
|
|
266
|
+
|
|
267
|
+
return (
|
|
268
|
+
<Link to={`/posts/${postId}`} onMouseEnter={prefetch}>
|
|
269
|
+
View Post
|
|
270
|
+
</Link>
|
|
271
|
+
);
|
|
272
|
+
}
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
## Best Practices
|
|
276
|
+
|
|
277
|
+
| Practice | Description |
|
|
278
|
+
|----------|-------------|
|
|
279
|
+
| **Structured Query Keys** | Use consistent, hierarchical query keys |
|
|
280
|
+
| **Appropriate Stale Times** | Set stale times per query type based on data volatility |
|
|
281
|
+
| **Enabled Option** | Control query execution with `enabled` |
|
|
282
|
+
| **Error Handling** | Handle errors at both query and component level |
|
|
283
|
+
| **Optimistic Updates** | Improve UX with optimistic updates on mutations |
|
|
284
|
+
| **Prefetching** | Prefetch data on hover or route transitions |
|
|
285
|
+
| **DevTools** | Use React Query DevTools for debugging |
|
|
286
|
+
|
|
287
|
+
## When to Use
|
|
288
|
+
|
|
289
|
+
- Any application with server data
|
|
290
|
+
- REST or GraphQL APIs
|
|
291
|
+
- Real-time data with polling
|
|
292
|
+
- Complex caching requirements
|
|
293
|
+
- Background data synchronization
|
|
294
|
+
|
|
295
|
+
## Related Skills
|
|
296
|
+
|
|
297
|
+
- `redux-toolkit-patterns` - For client state
|
|
298
|
+
- `zustand-patterns` - For client state
|
|
299
|
+
- `api-app-testing` - Test queries and mutations
|
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: zustand-patterns
|
|
3
|
+
category: state
|
|
4
|
+
description: Zustand lightweight state management patterns for React applications
|
|
5
|
+
usage: Use when implementing simple global client state without Redux boilerplate
|
|
6
|
+
input: State structure, actions, persistence needs
|
|
7
|
+
output: Store definitions, hooks, middleware configuration
|
|
8
|
+
config_required:
|
|
9
|
+
state_organization: "How state is organized (single store vs slices)"
|
|
10
|
+
persistence: "What state needs to be persisted"
|
|
11
|
+
storage_type: "Storage mechanism for persistence"
|
|
12
|
+
devtools: "Whether to enable Redux DevTools integration"
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
# Zustand Patterns
|
|
16
|
+
|
|
17
|
+
## ⚙️ Configuration
|
|
18
|
+
|
|
19
|
+
| Setting | Description | Example |
|
|
20
|
+
|---------|-------------|---------|
|
|
21
|
+
| `state_organization` | State organization strategy | Single store, multiple stores, slices |
|
|
22
|
+
| `persistence` | State to persist | User preferences, auth tokens, cart |
|
|
23
|
+
| `storage_type` | Storage mechanism | localStorage, sessionStorage, AsyncStorage |
|
|
24
|
+
| `devtools` | Redux DevTools integration | `true`, `false` |
|
|
25
|
+
|
|
26
|
+
## Purpose
|
|
27
|
+
|
|
28
|
+
Implement lightweight state management with:
|
|
29
|
+
- Minimal boilerplate
|
|
30
|
+
- TypeScript-first design
|
|
31
|
+
- Works outside React components
|
|
32
|
+
- Middleware support (persist, immer, devtools)
|
|
33
|
+
- No providers needed
|
|
34
|
+
|
|
35
|
+
## Basic Store
|
|
36
|
+
|
|
37
|
+
```typescript
|
|
38
|
+
import { create } from 'zustand';
|
|
39
|
+
|
|
40
|
+
interface CounterState {
|
|
41
|
+
count: number;
|
|
42
|
+
increment: () => void;
|
|
43
|
+
decrement: () => void;
|
|
44
|
+
reset: () => void;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export const useCounterStore = create<CounterState>((set) => ({
|
|
48
|
+
count: 0,
|
|
49
|
+
increment: () => set((state) => ({ count: state.count + 1 })),
|
|
50
|
+
decrement: () => set((state) => ({ count: state.count - 1 })),
|
|
51
|
+
reset: () => set({ count: 0 }),
|
|
52
|
+
}));
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Async Actions
|
|
56
|
+
|
|
57
|
+
```typescript
|
|
58
|
+
interface UserState {
|
|
59
|
+
user: User | null;
|
|
60
|
+
isLoading: boolean;
|
|
61
|
+
error: string | null;
|
|
62
|
+
fetchUser: (id: string) => Promise<void>;
|
|
63
|
+
updateUser: (data: Partial<User>) => Promise<void>;
|
|
64
|
+
clearUser: () => void;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export const useUserStore = create<UserState>((set, get) => ({
|
|
68
|
+
user: null,
|
|
69
|
+
isLoading: false,
|
|
70
|
+
error: null,
|
|
71
|
+
|
|
72
|
+
fetchUser: async (id: string) => {
|
|
73
|
+
set({ isLoading: true, error: null });
|
|
74
|
+
try {
|
|
75
|
+
const response = await fetch(`/api/users/${id}`);
|
|
76
|
+
if (!response.ok) throw new Error('Failed to fetch');
|
|
77
|
+
const user = await response.json();
|
|
78
|
+
set({ user, isLoading: false });
|
|
79
|
+
} catch (error) {
|
|
80
|
+
set({ error: (error as Error).message, isLoading: false });
|
|
81
|
+
}
|
|
82
|
+
},
|
|
83
|
+
|
|
84
|
+
updateUser: async (data: Partial<User>) => {
|
|
85
|
+
const currentUser = get().user;
|
|
86
|
+
if (!currentUser) return;
|
|
87
|
+
|
|
88
|
+
set({ isLoading: true, error: null });
|
|
89
|
+
try {
|
|
90
|
+
const response = await fetch(`/api/users/${currentUser.id}`, {
|
|
91
|
+
method: 'PATCH',
|
|
92
|
+
body: JSON.stringify(data),
|
|
93
|
+
});
|
|
94
|
+
const updatedUser = await response.json();
|
|
95
|
+
set({ user: updatedUser, isLoading: false });
|
|
96
|
+
} catch (error) {
|
|
97
|
+
set({ error: (error as Error).message, isLoading: false });
|
|
98
|
+
}
|
|
99
|
+
},
|
|
100
|
+
|
|
101
|
+
clearUser: () => set({ user: null, error: null }),
|
|
102
|
+
}));
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## Slices Pattern
|
|
106
|
+
|
|
107
|
+
```typescript
|
|
108
|
+
import { create, StateCreator } from 'zustand';
|
|
109
|
+
|
|
110
|
+
// Auth slice
|
|
111
|
+
interface AuthSlice {
|
|
112
|
+
token: string | null;
|
|
113
|
+
isAuthenticated: boolean;
|
|
114
|
+
login: (token: string) => void;
|
|
115
|
+
logout: () => void;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const createAuthSlice: StateCreator<AppStore, [], [], AuthSlice> = (set) => ({
|
|
119
|
+
token: null,
|
|
120
|
+
isAuthenticated: false,
|
|
121
|
+
login: (token) => set({ token, isAuthenticated: true }),
|
|
122
|
+
logout: () => set({ token: null, isAuthenticated: false }),
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
// User slice
|
|
126
|
+
interface UserSlice {
|
|
127
|
+
user: User | null;
|
|
128
|
+
setUser: (user: User | null) => void;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const createUserSlice: StateCreator<AppStore, [], [], UserSlice> = (set) => ({
|
|
132
|
+
user: null,
|
|
133
|
+
setUser: (user) => set({ user }),
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
// Combined store
|
|
137
|
+
type AppStore = AuthSlice & UserSlice;
|
|
138
|
+
|
|
139
|
+
export const useAppStore = create<AppStore>()((...args) => ({
|
|
140
|
+
...createAuthSlice(...args),
|
|
141
|
+
...createUserSlice(...args),
|
|
142
|
+
}));
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
## Middleware
|
|
146
|
+
|
|
147
|
+
### Persist
|
|
148
|
+
|
|
149
|
+
```typescript
|
|
150
|
+
import { create } from 'zustand';
|
|
151
|
+
import { persist, createJSONStorage } from 'zustand/middleware';
|
|
152
|
+
|
|
153
|
+
interface SettingsState {
|
|
154
|
+
theme: 'light' | 'dark';
|
|
155
|
+
language: string;
|
|
156
|
+
notifications: boolean;
|
|
157
|
+
setTheme: (theme: 'light' | 'dark') => void;
|
|
158
|
+
setLanguage: (lang: string) => void;
|
|
159
|
+
toggleNotifications: () => void;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export const useSettingsStore = create<SettingsState>()(
|
|
163
|
+
persist(
|
|
164
|
+
(set) => ({
|
|
165
|
+
theme: 'light',
|
|
166
|
+
language: 'en',
|
|
167
|
+
notifications: true,
|
|
168
|
+
setTheme: (theme) => set({ theme }),
|
|
169
|
+
setLanguage: (language) => set({ language }),
|
|
170
|
+
toggleNotifications: () => set((state) => ({ notifications: !state.notifications })),
|
|
171
|
+
}),
|
|
172
|
+
{
|
|
173
|
+
name: 'settings-storage',
|
|
174
|
+
storage: createJSONStorage(() => localStorage),
|
|
175
|
+
partialize: (state) => ({
|
|
176
|
+
theme: state.theme,
|
|
177
|
+
language: state.language,
|
|
178
|
+
notifications: state.notifications,
|
|
179
|
+
}),
|
|
180
|
+
}
|
|
181
|
+
)
|
|
182
|
+
);
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
### Immer
|
|
186
|
+
|
|
187
|
+
```typescript
|
|
188
|
+
import { create } from 'zustand';
|
|
189
|
+
import { immer } from 'zustand/middleware/immer';
|
|
190
|
+
|
|
191
|
+
interface TodoState {
|
|
192
|
+
todos: Todo[];
|
|
193
|
+
addTodo: (text: string) => void;
|
|
194
|
+
toggleTodo: (id: string) => void;
|
|
195
|
+
removeTodo: (id: string) => void;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
export const useTodoStore = create<TodoState>()(
|
|
199
|
+
immer((set) => ({
|
|
200
|
+
todos: [],
|
|
201
|
+
addTodo: (text) =>
|
|
202
|
+
set((state) => {
|
|
203
|
+
state.todos.push({
|
|
204
|
+
id: crypto.randomUUID(),
|
|
205
|
+
text,
|
|
206
|
+
completed: false,
|
|
207
|
+
});
|
|
208
|
+
}),
|
|
209
|
+
toggleTodo: (id) =>
|
|
210
|
+
set((state) => {
|
|
211
|
+
const todo = state.todos.find((t) => t.id === id);
|
|
212
|
+
if (todo) todo.completed = !todo.completed;
|
|
213
|
+
}),
|
|
214
|
+
removeTodo: (id) =>
|
|
215
|
+
set((state) => {
|
|
216
|
+
state.todos = state.todos.filter((t) => t.id !== id);
|
|
217
|
+
}),
|
|
218
|
+
}))
|
|
219
|
+
);
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
## Usage Patterns
|
|
223
|
+
|
|
224
|
+
### Selecting State
|
|
225
|
+
|
|
226
|
+
```typescript
|
|
227
|
+
// Select single value (re-renders only when count changes)
|
|
228
|
+
const count = useCounterStore((state) => state.count);
|
|
229
|
+
|
|
230
|
+
// Select action (stable reference)
|
|
231
|
+
const increment = useCounterStore((state) => state.increment);
|
|
232
|
+
|
|
233
|
+
// Select multiple with shallow comparison
|
|
234
|
+
import { useShallow } from 'zustand/react/shallow';
|
|
235
|
+
|
|
236
|
+
const { user, isLoading } = useUserStore(
|
|
237
|
+
useShallow((state) => ({ user: state.user, isLoading: state.isLoading }))
|
|
238
|
+
);
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
### Outside React
|
|
242
|
+
|
|
243
|
+
```typescript
|
|
244
|
+
// Get current state
|
|
245
|
+
const currentCount = useCounterStore.getState().count;
|
|
246
|
+
|
|
247
|
+
// Call actions
|
|
248
|
+
useCounterStore.getState().increment();
|
|
249
|
+
|
|
250
|
+
// Subscribe to changes
|
|
251
|
+
const unsubscribe = useCounterStore.subscribe((state) => {
|
|
252
|
+
console.log('Count changed:', state.count);
|
|
253
|
+
});
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
### Computed Values
|
|
257
|
+
|
|
258
|
+
```typescript
|
|
259
|
+
interface CartState {
|
|
260
|
+
items: CartItem[];
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
export const useCartStore = create<CartState>(() => ({
|
|
264
|
+
items: [],
|
|
265
|
+
}));
|
|
266
|
+
|
|
267
|
+
// Use selectors for computed values
|
|
268
|
+
export const useCartTotal = () =>
|
|
269
|
+
useCartStore((state) =>
|
|
270
|
+
state.items.reduce((sum, item) => sum + item.price * item.quantity, 0)
|
|
271
|
+
);
|
|
272
|
+
|
|
273
|
+
export const useCartItemCount = () =>
|
|
274
|
+
useCartStore((state) =>
|
|
275
|
+
state.items.reduce((sum, item) => sum + item.quantity, 0)
|
|
276
|
+
);
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
## Best Practices
|
|
280
|
+
|
|
281
|
+
| Practice | Description |
|
|
282
|
+
|----------|-------------|
|
|
283
|
+
| **Use Selectors** | Always use selectors to minimize re-renders |
|
|
284
|
+
| **Actions in Store** | Keep actions in the store, not components |
|
|
285
|
+
| **TypeScript** | Use TypeScript interfaces for full type safety |
|
|
286
|
+
| **Slices for Large Stores** | Split large stores into slices |
|
|
287
|
+
| **Persist User Preferences** | Use persist middleware for settings |
|
|
288
|
+
| **Immer for Nested Updates** | Use immer for complex nested state |
|
|
289
|
+
|
|
290
|
+
## When to Use
|
|
291
|
+
|
|
292
|
+
- Small to medium React applications
|
|
293
|
+
- When Redux is overkill
|
|
294
|
+
- Need state outside React components
|
|
295
|
+
- Simple global state needs
|
|
296
|
+
- With TanStack Query for server state
|
|
297
|
+
|
|
298
|
+
## Related Skills
|
|
299
|
+
|
|
300
|
+
- `redux-toolkit-patterns` - For complex state needs
|
|
301
|
+
- `tanstack-query-patterns` - For server state
|