@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,359 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: react-hook-form-patterns
|
|
3
|
+
category: react
|
|
4
|
+
description: React Hook Form patterns with Zod validation for performant forms
|
|
5
|
+
usage: Use when building forms with validation, complex schemas, and minimal re-renders
|
|
6
|
+
input: Form schema, validation rules, field types
|
|
7
|
+
output: Form components, validation schemas, submit handlers
|
|
8
|
+
config_required:
|
|
9
|
+
validation_library: "Validation library being used"
|
|
10
|
+
ui_library: "UI component library if any"
|
|
11
|
+
validation_mode: "When to validate (onChange, onBlur, onSubmit)"
|
|
12
|
+
default_values: "Default form values structure"
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
# React Hook Form Patterns
|
|
16
|
+
|
|
17
|
+
## ⚙️ Configuration
|
|
18
|
+
|
|
19
|
+
| Setting | Description | Example |
|
|
20
|
+
|---------|-------------|---------|
|
|
21
|
+
| `validation_library` | Schema validation library | Zod, Yup, Joi |
|
|
22
|
+
| `ui_library` | UI component library | Shadcn UI, Material UI, Chakra UI |
|
|
23
|
+
| `validation_mode` | Validation trigger | `onBlur`, `onChange`, `onSubmit` |
|
|
24
|
+
| `default_values` | Default form values | Empty object, populated data |
|
|
25
|
+
|
|
26
|
+
## Purpose
|
|
27
|
+
|
|
28
|
+
Build performant forms with:
|
|
29
|
+
- Minimal re-renders
|
|
30
|
+
- Type-safe validation with Zod
|
|
31
|
+
- Support for complex nested schemas
|
|
32
|
+
- Arrays with dynamic fields
|
|
33
|
+
- Integration with UI libraries
|
|
34
|
+
|
|
35
|
+
## Basic Form
|
|
36
|
+
|
|
37
|
+
```typescript
|
|
38
|
+
import { useForm } from 'react-hook-form';
|
|
39
|
+
import { zodResolver } from '@hookform/resolvers/zod';
|
|
40
|
+
import { z } from 'zod';
|
|
41
|
+
|
|
42
|
+
const loginSchema = z.object({
|
|
43
|
+
email: z.string().email('Invalid email'),
|
|
44
|
+
password: z.string().min(8, 'Min 8 characters'),
|
|
45
|
+
rememberMe: z.boolean().optional(),
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
type LoginFormData = z.infer<typeof loginSchema>;
|
|
49
|
+
|
|
50
|
+
function LoginForm() {
|
|
51
|
+
const {
|
|
52
|
+
register,
|
|
53
|
+
handleSubmit,
|
|
54
|
+
formState: { errors, isSubmitting },
|
|
55
|
+
} = useForm<LoginFormData>({
|
|
56
|
+
resolver: zodResolver(loginSchema),
|
|
57
|
+
defaultValues: {
|
|
58
|
+
email: '',
|
|
59
|
+
password: '',
|
|
60
|
+
rememberMe: false,
|
|
61
|
+
},
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
const onSubmit = async (data: LoginFormData) => {
|
|
65
|
+
await loginUser(data);
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
return (
|
|
69
|
+
<form onSubmit={handleSubmit(onSubmit)}>
|
|
70
|
+
<input {...register('email')} aria-invalid={errors.email ? 'true' : 'false'} />
|
|
71
|
+
{errors.email && <span role="alert">{errors.email.message}</span>}
|
|
72
|
+
|
|
73
|
+
<input type="password" {...register('password')} />
|
|
74
|
+
{errors.password && <span>{errors.password.message}</span>}
|
|
75
|
+
|
|
76
|
+
<label>
|
|
77
|
+
<input type="checkbox" {...register('rememberMe')} />
|
|
78
|
+
Remember me
|
|
79
|
+
</label>
|
|
80
|
+
|
|
81
|
+
<button type="submit" disabled={isSubmitting}>
|
|
82
|
+
{isSubmitting ? 'Logging in...' : 'Log in'}
|
|
83
|
+
</button>
|
|
84
|
+
</form>
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## Complex Schemas
|
|
90
|
+
|
|
91
|
+
### Nested Objects
|
|
92
|
+
|
|
93
|
+
```typescript
|
|
94
|
+
const userSchema = z.object({
|
|
95
|
+
firstName: z.string().min(1),
|
|
96
|
+
lastName: z.string().min(1),
|
|
97
|
+
address: z.object({
|
|
98
|
+
street: z.string().min(1),
|
|
99
|
+
city: z.string().min(1),
|
|
100
|
+
zipCode: z.string().regex(/^\d{5}$/),
|
|
101
|
+
}),
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
type UserFormData = z.infer<typeof userSchema>;
|
|
105
|
+
|
|
106
|
+
function UserForm() {
|
|
107
|
+
const { register, formState: { errors } } = useForm<UserFormData>({
|
|
108
|
+
resolver: zodResolver(userSchema),
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
return (
|
|
112
|
+
<form>
|
|
113
|
+
<input {...register('firstName')} />
|
|
114
|
+
<input {...register('address.street')} />
|
|
115
|
+
<input {...register('address.city')} />
|
|
116
|
+
{errors.address?.street && <span>{errors.address.street.message}</span>}
|
|
117
|
+
</form>
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
### Arrays with useFieldArray
|
|
123
|
+
|
|
124
|
+
```typescript
|
|
125
|
+
const orderSchema = z.object({
|
|
126
|
+
items: z.array(
|
|
127
|
+
z.object({
|
|
128
|
+
productId: z.string().min(1),
|
|
129
|
+
quantity: z.number().min(1),
|
|
130
|
+
notes: z.string().optional(),
|
|
131
|
+
})
|
|
132
|
+
).min(1, 'At least one item required'),
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
type OrderFormData = z.infer<typeof orderSchema>;
|
|
136
|
+
|
|
137
|
+
function OrderForm() {
|
|
138
|
+
const { control, register, handleSubmit, formState: { errors } } = useForm<OrderFormData>({
|
|
139
|
+
resolver: zodResolver(orderSchema),
|
|
140
|
+
defaultValues: {
|
|
141
|
+
items: [{ productId: '', quantity: 1, notes: '' }],
|
|
142
|
+
},
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
const { fields, append, remove } = useFieldArray({
|
|
146
|
+
control,
|
|
147
|
+
name: 'items',
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
return (
|
|
151
|
+
<form onSubmit={handleSubmit(onSubmit)}>
|
|
152
|
+
{fields.map((field, index) => (
|
|
153
|
+
<div key={field.id}>
|
|
154
|
+
<input {...register(`items.${index}.productId`)} />
|
|
155
|
+
<input type="number" {...register(`items.${index}.quantity`, { valueAsNumber: true })} />
|
|
156
|
+
<button type="button" onClick={() => remove(index)}>Remove</button>
|
|
157
|
+
{errors.items?.[index]?.productId && (
|
|
158
|
+
<span>{errors.items[index]?.productId?.message}</span>
|
|
159
|
+
)}
|
|
160
|
+
</div>
|
|
161
|
+
))}
|
|
162
|
+
<button type="button" onClick={() => append({ productId: '', quantity: 1, notes: '' })}>
|
|
163
|
+
Add Item
|
|
164
|
+
</button>
|
|
165
|
+
<button type="submit">Submit</button>
|
|
166
|
+
</form>
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
## Controlled Components
|
|
172
|
+
|
|
173
|
+
```typescript
|
|
174
|
+
import { Controller, useForm } from 'react-hook-form';
|
|
175
|
+
import { Select, DatePicker } from '@/components/ui';
|
|
176
|
+
|
|
177
|
+
const eventSchema = z.object({
|
|
178
|
+
title: z.string().min(1),
|
|
179
|
+
date: z.date(),
|
|
180
|
+
category: z.enum(['meeting', 'deadline', 'reminder']),
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
function EventForm() {
|
|
184
|
+
const { control, handleSubmit } = useForm({
|
|
185
|
+
resolver: zodResolver(eventSchema),
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
return (
|
|
189
|
+
<form onSubmit={handleSubmit(onSubmit)}>
|
|
190
|
+
<Controller
|
|
191
|
+
name="date"
|
|
192
|
+
control={control}
|
|
193
|
+
render={({ field, fieldState: { error } }) => (
|
|
194
|
+
<DatePicker
|
|
195
|
+
selected={field.value}
|
|
196
|
+
onChange={field.onChange}
|
|
197
|
+
error={error?.message}
|
|
198
|
+
/>
|
|
199
|
+
)}
|
|
200
|
+
/>
|
|
201
|
+
|
|
202
|
+
<Controller
|
|
203
|
+
name="category"
|
|
204
|
+
control={control}
|
|
205
|
+
render={({ field }) => (
|
|
206
|
+
<Select
|
|
207
|
+
value={field.value}
|
|
208
|
+
onValueChange={field.onChange}
|
|
209
|
+
options={[
|
|
210
|
+
{ label: 'Meeting', value: 'meeting' },
|
|
211
|
+
{ label: 'Deadline', value: 'deadline' },
|
|
212
|
+
]}
|
|
213
|
+
/>
|
|
214
|
+
)}
|
|
215
|
+
/>
|
|
216
|
+
</form>
|
|
217
|
+
);
|
|
218
|
+
}
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
## UI Library Integration
|
|
222
|
+
|
|
223
|
+
```typescript
|
|
224
|
+
import { useForm } from 'react-hook-form';
|
|
225
|
+
import { zodResolver } from '@hookform/resolvers/zod';
|
|
226
|
+
import {
|
|
227
|
+
Form,
|
|
228
|
+
FormField,
|
|
229
|
+
FormItem,
|
|
230
|
+
FormLabel,
|
|
231
|
+
FormControl,
|
|
232
|
+
FormMessage,
|
|
233
|
+
} from '@/components/ui/form';
|
|
234
|
+
|
|
235
|
+
function ProfileForm() {
|
|
236
|
+
const form = useForm({
|
|
237
|
+
resolver: zodResolver(profileSchema),
|
|
238
|
+
defaultValues: { username: '', bio: '' },
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
return (
|
|
242
|
+
<Form {...form}>
|
|
243
|
+
<form onSubmit={form.handleSubmit(onSubmit)}>
|
|
244
|
+
<FormField
|
|
245
|
+
control={form.control}
|
|
246
|
+
name="username"
|
|
247
|
+
render={({ field }) => (
|
|
248
|
+
<FormItem>
|
|
249
|
+
<FormLabel>Username</FormLabel>
|
|
250
|
+
<FormControl>
|
|
251
|
+
<Input placeholder="johndoe" {...field} />
|
|
252
|
+
</FormControl>
|
|
253
|
+
<FormMessage />
|
|
254
|
+
</FormItem>
|
|
255
|
+
)}
|
|
256
|
+
/>
|
|
257
|
+
<Button type="submit">Submit</Button>
|
|
258
|
+
</form>
|
|
259
|
+
</Form>
|
|
260
|
+
);
|
|
261
|
+
}
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
## Advanced Patterns
|
|
265
|
+
|
|
266
|
+
### File Upload
|
|
267
|
+
|
|
268
|
+
```typescript
|
|
269
|
+
const uploadSchema = z.object({
|
|
270
|
+
title: z.string().min(1),
|
|
271
|
+
file: z
|
|
272
|
+
.instanceof(FileList)
|
|
273
|
+
.refine((files) => files.length > 0, 'File required')
|
|
274
|
+
.refine((files) => files[0]?.size <= 5 * 1024 * 1024, 'Max 5MB')
|
|
275
|
+
.refine(
|
|
276
|
+
(files) => ['image/jpeg', 'image/png'].includes(files[0]?.type),
|
|
277
|
+
'Only JPEG/PNG allowed'
|
|
278
|
+
),
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
function UploadForm() {
|
|
282
|
+
const { register, handleSubmit } = useForm({
|
|
283
|
+
resolver: zodResolver(uploadSchema),
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
const onSubmit = async (data: z.infer<typeof uploadSchema>) => {
|
|
287
|
+
const formData = new FormData();
|
|
288
|
+
formData.append('title', data.title);
|
|
289
|
+
formData.append('file', data.file[0]);
|
|
290
|
+
await uploadFile(formData);
|
|
291
|
+
};
|
|
292
|
+
|
|
293
|
+
return (
|
|
294
|
+
<form onSubmit={handleSubmit(onSubmit)}>
|
|
295
|
+
<input {...register('title')} />
|
|
296
|
+
<input type="file" {...register('file')} accept="image/jpeg,image/png" />
|
|
297
|
+
<button type="submit">Upload</button>
|
|
298
|
+
</form>
|
|
299
|
+
);
|
|
300
|
+
}
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
### Async Validation
|
|
304
|
+
|
|
305
|
+
```typescript
|
|
306
|
+
function RegisterForm() {
|
|
307
|
+
const form = useForm({
|
|
308
|
+
resolver: zodResolver(registerSchema),
|
|
309
|
+
mode: 'onBlur',
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
const validateUsername = async (username: string) => {
|
|
313
|
+
const response = await fetch(`/api/check-username?username=${username}`);
|
|
314
|
+
const { available } = await response.json();
|
|
315
|
+
if (!available) {
|
|
316
|
+
form.setError('username', {
|
|
317
|
+
type: 'manual',
|
|
318
|
+
message: 'Username already taken',
|
|
319
|
+
});
|
|
320
|
+
return false;
|
|
321
|
+
}
|
|
322
|
+
return true;
|
|
323
|
+
};
|
|
324
|
+
|
|
325
|
+
return (
|
|
326
|
+
<form>
|
|
327
|
+
<input
|
|
328
|
+
{...form.register('username')}
|
|
329
|
+
onBlur={(e) => validateUsername(e.target.value)}
|
|
330
|
+
/>
|
|
331
|
+
</form>
|
|
332
|
+
);
|
|
333
|
+
}
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
## Best Practices
|
|
337
|
+
|
|
338
|
+
| Practice | Description |
|
|
339
|
+
|----------|-------------|
|
|
340
|
+
| **Separate Schemas** | Define schemas separately for reuse and testing |
|
|
341
|
+
| **Type Inference** | Use `z.infer<typeof schema>` for types |
|
|
342
|
+
| **Validation Mode** | Use `mode: 'onBlur'` for better UX |
|
|
343
|
+
| **Controller for Custom** | Use Controller for controlled components |
|
|
344
|
+
| **useFieldArray** | Use for dynamic arrays |
|
|
345
|
+
| **Accessible Errors** | Show errors with accessible markup |
|
|
346
|
+
|
|
347
|
+
## When to Use
|
|
348
|
+
|
|
349
|
+
- Any React form with validation
|
|
350
|
+
- Complex multi-step forms
|
|
351
|
+
- Forms with dynamic fields
|
|
352
|
+
- Need minimal re-renders
|
|
353
|
+
- Integration with UI libraries
|
|
354
|
+
|
|
355
|
+
## Related Skills
|
|
356
|
+
|
|
357
|
+
- `error-handling-patterns` - Handle form errors
|
|
358
|
+
- `web-app-testing` - Test forms
|
|
359
|
+
- `shadcn-specialist` - Shadcn UI integration
|
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: redux-toolkit-patterns
|
|
3
|
+
category: state
|
|
4
|
+
description: Redux Toolkit state management patterns for React applications
|
|
5
|
+
usage: Use when implementing global state with Redux Toolkit and RTK Query
|
|
6
|
+
input: State structure, async operations, API endpoints
|
|
7
|
+
output: Store configuration, slices, async thunks, RTK Query APIs
|
|
8
|
+
config_required:
|
|
9
|
+
state_structure: "Organization of state slices"
|
|
10
|
+
middleware: "Additional middleware beyond RTK defaults"
|
|
11
|
+
dev_tools: "Redux DevTools configuration"
|
|
12
|
+
api_base_url: "Base URL for RTK Query"
|
|
13
|
+
cache_tags: "Tag types for cache invalidation"
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
# Redux Toolkit Patterns
|
|
17
|
+
|
|
18
|
+
## ⚙️ Configuration
|
|
19
|
+
|
|
20
|
+
| Setting | Description | Example |
|
|
21
|
+
|---------|-------------|---------|
|
|
22
|
+
| `state_structure` | How state is organized into slices | `auth`, `user`, `cart`, `settings` |
|
|
23
|
+
| `middleware` | Additional middleware | `logger`, custom middleware |
|
|
24
|
+
| `dev_tools` | DevTools settings | `enabled: true`, trace limit |
|
|
25
|
+
| `api_base_url` | RTK Query base URL | `/api`, `https://api.example.com` |
|
|
26
|
+
| `cache_tags` | Cache invalidation tags | `['User', 'Post', 'Comment']` |
|
|
27
|
+
|
|
28
|
+
## Purpose
|
|
29
|
+
|
|
30
|
+
Implement Redux state management with:
|
|
31
|
+
- Simplified Redux setup with RTK
|
|
32
|
+
- Type-safe state and actions
|
|
33
|
+
- Built-in Immer for immutable updates
|
|
34
|
+
- RTK Query for server state
|
|
35
|
+
- Async thunk patterns
|
|
36
|
+
|
|
37
|
+
## Store Setup
|
|
38
|
+
|
|
39
|
+
```typescript
|
|
40
|
+
import { configureStore } from '@reduxjs/toolkit';
|
|
41
|
+
import { setupListeners } from '@reduxjs/toolkit/query';
|
|
42
|
+
|
|
43
|
+
export const store = configureStore({
|
|
44
|
+
reducer: {
|
|
45
|
+
auth: authReducer,
|
|
46
|
+
user: userReducer,
|
|
47
|
+
[api.reducerPath]: api.reducer,
|
|
48
|
+
},
|
|
49
|
+
middleware: (getDefaultMiddleware) =>
|
|
50
|
+
getDefaultMiddleware().concat(api.middleware),
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
setupListeners(store.dispatch);
|
|
54
|
+
|
|
55
|
+
export type RootState = ReturnType<typeof store.getState>;
|
|
56
|
+
export type AppDispatch = typeof store.dispatch;
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### Typed Hooks
|
|
60
|
+
|
|
61
|
+
```typescript
|
|
62
|
+
import { useDispatch, useSelector, TypedUseSelectorHook } from 'react-redux';
|
|
63
|
+
import type { RootState, AppDispatch } from './store';
|
|
64
|
+
|
|
65
|
+
export const useAppDispatch = () => useDispatch<AppDispatch>();
|
|
66
|
+
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Slice Patterns
|
|
70
|
+
|
|
71
|
+
### Basic Slice
|
|
72
|
+
|
|
73
|
+
```typescript
|
|
74
|
+
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
|
|
75
|
+
|
|
76
|
+
interface CounterState {
|
|
77
|
+
value: number;
|
|
78
|
+
status: 'idle' | 'loading' | 'failed';
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const initialState: CounterState = { value: 0, status: 'idle' };
|
|
82
|
+
|
|
83
|
+
export const counterSlice = createSlice({
|
|
84
|
+
name: 'counter',
|
|
85
|
+
initialState,
|
|
86
|
+
reducers: {
|
|
87
|
+
increment: (state) => { state.value += 1; },
|
|
88
|
+
decrement: (state) => { state.value -= 1; },
|
|
89
|
+
incrementByAmount: (state, action: PayloadAction<number>) => {
|
|
90
|
+
state.value += action.payload;
|
|
91
|
+
},
|
|
92
|
+
reset: () => initialState,
|
|
93
|
+
},
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
export const { increment, decrement, incrementByAmount, reset } = counterSlice.actions;
|
|
97
|
+
export const counterReducer = counterSlice.reducer;
|
|
98
|
+
export const selectCount = (state: RootState) => state.counter.value;
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### Async Thunks
|
|
102
|
+
|
|
103
|
+
```typescript
|
|
104
|
+
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit';
|
|
105
|
+
|
|
106
|
+
interface UserState {
|
|
107
|
+
users: User[];
|
|
108
|
+
status: 'idle' | 'loading' | 'succeeded' | 'failed';
|
|
109
|
+
error: string | null;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Async thunk
|
|
113
|
+
export const fetchUsers = createAsyncThunk(
|
|
114
|
+
'user/fetchUsers',
|
|
115
|
+
async (_, { rejectWithValue }) => {
|
|
116
|
+
try {
|
|
117
|
+
const response = await fetch('/api/users');
|
|
118
|
+
if (!response.ok) throw new Error('Failed to fetch');
|
|
119
|
+
return (await response.json()) as User[];
|
|
120
|
+
} catch (error) {
|
|
121
|
+
return rejectWithValue((error as Error).message);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
);
|
|
125
|
+
|
|
126
|
+
const userSlice = createSlice({
|
|
127
|
+
name: 'user',
|
|
128
|
+
initialState,
|
|
129
|
+
reducers: {},
|
|
130
|
+
extraReducers: (builder) => {
|
|
131
|
+
builder
|
|
132
|
+
.addCase(fetchUsers.pending, (state) => {
|
|
133
|
+
state.status = 'loading';
|
|
134
|
+
})
|
|
135
|
+
.addCase(fetchUsers.fulfilled, (state, action) => {
|
|
136
|
+
state.status = 'succeeded';
|
|
137
|
+
state.users = action.payload;
|
|
138
|
+
})
|
|
139
|
+
.addCase(fetchUsers.rejected, (state, action) => {
|
|
140
|
+
state.status = 'failed';
|
|
141
|
+
state.error = action.payload as string;
|
|
142
|
+
});
|
|
143
|
+
},
|
|
144
|
+
});
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
## RTK Query
|
|
148
|
+
|
|
149
|
+
```typescript
|
|
150
|
+
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react';
|
|
151
|
+
|
|
152
|
+
export const api = createApi({
|
|
153
|
+
reducerPath: 'api',
|
|
154
|
+
baseQuery: fetchBaseQuery({
|
|
155
|
+
baseUrl: '/api',
|
|
156
|
+
prepareHeaders: (headers, { getState }) => {
|
|
157
|
+
const token = (getState() as RootState).auth.token;
|
|
158
|
+
if (token) headers.set('Authorization', `Bearer ${token}`);
|
|
159
|
+
return headers;
|
|
160
|
+
},
|
|
161
|
+
}),
|
|
162
|
+
tagTypes: ['User', 'Post'],
|
|
163
|
+
endpoints: (builder) => ({
|
|
164
|
+
// Queries
|
|
165
|
+
getUsers: builder.query<User[], void>({
|
|
166
|
+
query: () => '/users',
|
|
167
|
+
providesTags: (result) =>
|
|
168
|
+
result
|
|
169
|
+
? [...result.map(({ id }) => ({ type: 'User' as const, id })), { type: 'User', id: 'LIST' }]
|
|
170
|
+
: [{ type: 'User', id: 'LIST' }],
|
|
171
|
+
}),
|
|
172
|
+
|
|
173
|
+
getUserById: builder.query<User, string>({
|
|
174
|
+
query: (id) => `/users/${id}`,
|
|
175
|
+
providesTags: (result, error, id) => [{ type: 'User', id }],
|
|
176
|
+
}),
|
|
177
|
+
|
|
178
|
+
// Mutations
|
|
179
|
+
createUser: builder.mutation<User, Omit<User, 'id'>>({
|
|
180
|
+
query: (body) => ({
|
|
181
|
+
url: '/users',
|
|
182
|
+
method: 'POST',
|
|
183
|
+
body,
|
|
184
|
+
}),
|
|
185
|
+
invalidatesTags: [{ type: 'User', id: 'LIST' }],
|
|
186
|
+
}),
|
|
187
|
+
|
|
188
|
+
updateUser: builder.mutation<User, { id: string; data: Partial<User> }>({
|
|
189
|
+
query: ({ id, data }) => ({
|
|
190
|
+
url: `/users/${id}`,
|
|
191
|
+
method: 'PATCH',
|
|
192
|
+
body: data,
|
|
193
|
+
}),
|
|
194
|
+
invalidatesTags: (result, error, { id }) => [{ type: 'User', id }],
|
|
195
|
+
}),
|
|
196
|
+
|
|
197
|
+
deleteUser: builder.mutation<void, string>({
|
|
198
|
+
query: (id) => ({
|
|
199
|
+
url: `/users/${id}`,
|
|
200
|
+
method: 'DELETE',
|
|
201
|
+
}),
|
|
202
|
+
invalidatesTags: (result, error, id) => [{ type: 'User', id }, { type: 'User', id: 'LIST' }],
|
|
203
|
+
}),
|
|
204
|
+
}),
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
export const {
|
|
208
|
+
useGetUsersQuery,
|
|
209
|
+
useGetUserByIdQuery,
|
|
210
|
+
useCreateUserMutation,
|
|
211
|
+
useUpdateUserMutation,
|
|
212
|
+
useDeleteUserMutation,
|
|
213
|
+
} = api;
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
## Component Usage
|
|
217
|
+
|
|
218
|
+
```typescript
|
|
219
|
+
import { useAppDispatch, useAppSelector } from './store/hooks';
|
|
220
|
+
import { increment, selectCount } from './store/counterSlice';
|
|
221
|
+
|
|
222
|
+
function Counter() {
|
|
223
|
+
const dispatch = useAppDispatch();
|
|
224
|
+
const count = useAppSelector(selectCount);
|
|
225
|
+
|
|
226
|
+
return (
|
|
227
|
+
<div>
|
|
228
|
+
<span>{count}</span>
|
|
229
|
+
<button onClick={() => dispatch(increment())}>+</button>
|
|
230
|
+
</div>
|
|
231
|
+
);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// RTK Query usage
|
|
235
|
+
function UserList() {
|
|
236
|
+
const { data: users, isLoading, error, refetch } = useGetUsersQuery();
|
|
237
|
+
const [updateUser, { isLoading: isUpdating }] = useUpdateUserMutation();
|
|
238
|
+
|
|
239
|
+
if (isLoading) return <Spinner />;
|
|
240
|
+
if (error) return <Error />;
|
|
241
|
+
|
|
242
|
+
return (
|
|
243
|
+
<div>
|
|
244
|
+
<button onClick={refetch}>Refresh</button>
|
|
245
|
+
{users?.map((user) => <UserCard key={user.id} user={user} />)}
|
|
246
|
+
</div>
|
|
247
|
+
);
|
|
248
|
+
}
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
## Best Practices
|
|
252
|
+
|
|
253
|
+
| Practice | Description |
|
|
254
|
+
|----------|-------------|
|
|
255
|
+
| **Typed Hooks** | Always use typed `useAppDispatch` and `useAppSelector` |
|
|
256
|
+
| **Selectors** | Create memoized selectors with `createSelector` |
|
|
257
|
+
| **RTK Query for Server State** | Use RTK Query for server data, slices for client state |
|
|
258
|
+
| **Tag Invalidation** | Use proper tag invalidation for cache management |
|
|
259
|
+
| **Entity Adapter** | Use `createEntityAdapter` for normalized collections |
|
|
260
|
+
| **DevTools** | Enable Redux DevTools for debugging |
|
|
261
|
+
|
|
262
|
+
## When to Use
|
|
263
|
+
|
|
264
|
+
- Large-scale applications with complex state
|
|
265
|
+
- Teams needing predictable state management
|
|
266
|
+
- When you need time-travel debugging
|
|
267
|
+
- Applications combining server and client state
|
|
268
|
+
|
|
269
|
+
## Related Skills
|
|
270
|
+
|
|
271
|
+
- `tanstack-query-patterns` - Alternative for server state
|
|
272
|
+
- `zustand-patterns` - Simpler alternative for client state
|