@raftlabs/raftstack 1.0.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/skills/backend/SKILL.md +802 -0
- package/.claude/skills/code-quality/SKILL.md +318 -0
- package/.claude/skills/database/SKILL.md +465 -0
- package/.claude/skills/react/SKILL.md +418 -0
- package/.claude/skills/seo/SKILL.md +446 -0
- package/LICENSE +21 -0
- package/README.md +291 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +2009 -0
- package/dist/cli.js.map +1 -0
- package/package.json +69 -0
|
@@ -0,0 +1,418 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: react
|
|
3
|
+
description: Use when writing React components, using hooks, handling state, working with Next.js/Remix/Vite/Astro, or when components feel bloated, have unnecessary re-renders, or violate single responsibility
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# React Development
|
|
7
|
+
|
|
8
|
+
## Overview
|
|
9
|
+
|
|
10
|
+
Write components that are small, focused, and follow framework conventions. Detect the framework first - patterns differ significantly between Next.js App Router, Remix, Vite SPA, and Astro.
|
|
11
|
+
|
|
12
|
+
## When to Use
|
|
13
|
+
|
|
14
|
+
- Creating any React component
|
|
15
|
+
- Choosing state management approach
|
|
16
|
+
- Deciding client vs server rendering
|
|
17
|
+
- Optimizing re-renders
|
|
18
|
+
- Setting up data fetching
|
|
19
|
+
|
|
20
|
+
## Framework Detection
|
|
21
|
+
|
|
22
|
+
**Always check before writing React code:**
|
|
23
|
+
|
|
24
|
+
```typescript
|
|
25
|
+
// Check package.json for framework
|
|
26
|
+
import pkg from './package.json';
|
|
27
|
+
|
|
28
|
+
const framework = detectFramework(pkg.dependencies);
|
|
29
|
+
// 'next' → Next.js (check for app/ vs pages/)
|
|
30
|
+
// '@remix-run/react' → Remix
|
|
31
|
+
// 'astro' → Astro
|
|
32
|
+
// 'vite' only → Vite SPA
|
|
33
|
+
// none → Create React App or custom
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
| File/Config | Framework | Router Type |
|
|
37
|
+
|------------|-----------|-------------|
|
|
38
|
+
| `app/` directory + `next.config` | Next.js App Router | File-based, RSC |
|
|
39
|
+
| `pages/` directory + `next.config` | Next.js Pages Router | File-based, CSR/SSR |
|
|
40
|
+
| `remix.config.js` or `@remix-run/*` | Remix | Nested, loader-based |
|
|
41
|
+
| `astro.config.mjs` | Astro | Islands, mostly static |
|
|
42
|
+
| `vite.config.ts` only | Vite SPA | Client-side only |
|
|
43
|
+
|
|
44
|
+
## The Iron Rules
|
|
45
|
+
|
|
46
|
+
### 1. 'use client' Goes at File Top - NEVER Middle
|
|
47
|
+
|
|
48
|
+
```typescript
|
|
49
|
+
// ❌ FATAL: This breaks compilation
|
|
50
|
+
function ServerComponent() { ... }
|
|
51
|
+
|
|
52
|
+
'use client'; // WRONG - can't be after other code
|
|
53
|
+
|
|
54
|
+
function ClientComponent() { ... }
|
|
55
|
+
|
|
56
|
+
// ✅ CORRECT: Separate files
|
|
57
|
+
// -- server-component.tsx --
|
|
58
|
+
export function ServerComponent() { ... }
|
|
59
|
+
|
|
60
|
+
// -- client-component.tsx --
|
|
61
|
+
'use client';
|
|
62
|
+
export function ClientComponent() { ... }
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
**Never write "for demonstration" code that violates this.** Broken code teaches nothing.
|
|
66
|
+
|
|
67
|
+
### 2. Server Components by Default (Next.js App Router)
|
|
68
|
+
|
|
69
|
+
Data fetching belongs on the server. Only add 'use client' when you need:
|
|
70
|
+
- Event handlers (onClick, onChange)
|
|
71
|
+
- useState, useEffect, useReducer
|
|
72
|
+
- Browser APIs (localStorage, window)
|
|
73
|
+
|
|
74
|
+
**Keep 'use client' boundaries deep** to minimize JavaScript bundle size.
|
|
75
|
+
|
|
76
|
+
```typescript
|
|
77
|
+
// ✅ GOOD: Server Component fetches data
|
|
78
|
+
// app/products/page.tsx
|
|
79
|
+
async function ProductsPage() {
|
|
80
|
+
const products = await db.product.findMany(); // Direct DB access
|
|
81
|
+
return <ProductList products={products} />;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// ❌ BAD: Client Component fetches in useEffect
|
|
85
|
+
'use client';
|
|
86
|
+
function ProductsPage() {
|
|
87
|
+
const [products, setProducts] = useState([]);
|
|
88
|
+
useEffect(() => {
|
|
89
|
+
fetch('/api/products').then(...) // Extra round-trip
|
|
90
|
+
}, []);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// ✅ GOOD: Mixed - only Search is client
|
|
94
|
+
function Layout({ children }) {
|
|
95
|
+
return (
|
|
96
|
+
<nav>
|
|
97
|
+
<Logo /> {/* Server Component */}
|
|
98
|
+
<Search /> {/* 'use client' - only this interactive */}
|
|
99
|
+
</nav>
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### 3. Component Responsibilities: Max 3 Concerns
|
|
105
|
+
|
|
106
|
+
A component should handle at most 3 concerns:
|
|
107
|
+
1. Data → Layout → Interaction (orchestrator)
|
|
108
|
+
2. Fetch → Transform → Display (data component)
|
|
109
|
+
3. State → Render → Events (interactive component)
|
|
110
|
+
|
|
111
|
+
If you have more, split.
|
|
112
|
+
|
|
113
|
+
```typescript
|
|
114
|
+
// ❌ BAD: Too many concerns
|
|
115
|
+
function ProductPage() {
|
|
116
|
+
// Fetching
|
|
117
|
+
// Validation
|
|
118
|
+
// Cart logic
|
|
119
|
+
// Analytics
|
|
120
|
+
// Rendering
|
|
121
|
+
// Error handling
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// ✅ GOOD: Split by concern
|
|
125
|
+
function ProductPage() {
|
|
126
|
+
return (
|
|
127
|
+
<ProductDataProvider>
|
|
128
|
+
<ProductDisplay />
|
|
129
|
+
<AddToCartButton />
|
|
130
|
+
</ProductDataProvider>
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### 4. State Placement Decision Tree
|
|
136
|
+
|
|
137
|
+
```
|
|
138
|
+
Need state?
|
|
139
|
+
├── Used by 1 component → useState in that component
|
|
140
|
+
├── Used by siblings → lift to parent
|
|
141
|
+
├── Used across routes → URL params or context
|
|
142
|
+
├── Complex/async → useReducer or server state (TanStack Query)
|
|
143
|
+
└── Global → Zustand or Jotai (NOT Redux unless massive app)
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
### 5. Hooks Rules
|
|
147
|
+
|
|
148
|
+
```typescript
|
|
149
|
+
// ❌ BAD: Conditional hook
|
|
150
|
+
if (condition) {
|
|
151
|
+
useEffect(() => { ... }); // Breaks rules of hooks
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// ✅ GOOD: Condition inside hook
|
|
155
|
+
useEffect(() => {
|
|
156
|
+
if (!condition) return;
|
|
157
|
+
// ... effect logic
|
|
158
|
+
}, [condition]);
|
|
159
|
+
|
|
160
|
+
// ❌ BAD: Too many useState
|
|
161
|
+
const [name, setName] = useState('');
|
|
162
|
+
const [email, setEmail] = useState('');
|
|
163
|
+
const [error, setError] = useState(null);
|
|
164
|
+
const [loading, setLoading] = useState(false);
|
|
165
|
+
const [submitted, setSubmitted] = useState(false);
|
|
166
|
+
|
|
167
|
+
// ✅ GOOD: Related state in reducer
|
|
168
|
+
const [formState, dispatch] = useReducer(formReducer, initialState);
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
### 6. Server Actions for Forms (Next.js App Router)
|
|
172
|
+
|
|
173
|
+
Server Actions handle mutations without API routes. Use `useActionState` (React 19+) for form state.
|
|
174
|
+
|
|
175
|
+
```typescript
|
|
176
|
+
// ✅ GOOD: Server Action with validation
|
|
177
|
+
'use server';
|
|
178
|
+
import { z } from 'zod';
|
|
179
|
+
|
|
180
|
+
const schema = z.object({
|
|
181
|
+
email: z.string().email(),
|
|
182
|
+
name: z.string().min(2)
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
export async function createUser(prevState: any, formData: FormData) {
|
|
186
|
+
const validatedFields = schema.safeParse({
|
|
187
|
+
email: formData.get('email'),
|
|
188
|
+
name: formData.get('name')
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
if (!validatedFields.success) {
|
|
192
|
+
return { errors: validatedFields.error.flatten().fieldErrors };
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Return error object, DON'T throw
|
|
196
|
+
const result = await db.user.create(validatedFields.data);
|
|
197
|
+
if (!result.ok) return { message: 'Failed to create user' };
|
|
198
|
+
|
|
199
|
+
redirect('/users');
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Client Component using the action
|
|
203
|
+
'use client';
|
|
204
|
+
import { useActionState } from 'react';
|
|
205
|
+
|
|
206
|
+
export function SignupForm() {
|
|
207
|
+
const [state, formAction, isPending] = useActionState(createUser, { message: '' });
|
|
208
|
+
|
|
209
|
+
return (
|
|
210
|
+
<form action={formAction}>
|
|
211
|
+
<input type="email" name="email" required />
|
|
212
|
+
{state?.errors?.email && <p>{state.errors.email}</p>}
|
|
213
|
+
|
|
214
|
+
<input type="text" name="name" required />
|
|
215
|
+
{state?.errors?.name && <p>{state.errors.name}</p>}
|
|
216
|
+
|
|
217
|
+
<button disabled={isPending}>Sign up</button>
|
|
218
|
+
{state?.message && <p aria-live="polite">{state.message}</p>}
|
|
219
|
+
</form>
|
|
220
|
+
);
|
|
221
|
+
}
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
**Key patterns:**
|
|
225
|
+
- Server Actions return error objects, never throw
|
|
226
|
+
- Use `useActionState` for form state + pending + errors
|
|
227
|
+
- Progressive enhancement: works without JS
|
|
228
|
+
- Validate with Zod in Server Action
|
|
229
|
+
|
|
230
|
+
## React 19 Features
|
|
231
|
+
|
|
232
|
+
### use() Hook - Read Promises/Context
|
|
233
|
+
|
|
234
|
+
```typescript
|
|
235
|
+
import { use } from 'react';
|
|
236
|
+
|
|
237
|
+
// ✅ Read promise in component
|
|
238
|
+
function Message({ messagePromise }: { messagePromise: Promise<string> }) {
|
|
239
|
+
const message = use(messagePromise); // Suspends until resolved
|
|
240
|
+
return <p>{message}</p>;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// ✅ Read context without Consumer
|
|
244
|
+
function Button() {
|
|
245
|
+
const theme = use(ThemeContext);
|
|
246
|
+
return <button className={theme}>Click</button>;
|
|
247
|
+
}
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
### useOptimistic - Instant UI Updates
|
|
251
|
+
|
|
252
|
+
```typescript
|
|
253
|
+
'use client';
|
|
254
|
+
import { useOptimistic } from 'react';
|
|
255
|
+
|
|
256
|
+
function TodoList({ todos }: { todos: Todo[] }) {
|
|
257
|
+
const [optimisticTodos, addOptimistic] = useOptimistic(
|
|
258
|
+
todos,
|
|
259
|
+
(state, newTodo: Todo) => [...state, newTodo]
|
|
260
|
+
);
|
|
261
|
+
|
|
262
|
+
async function handleAdd(formData: FormData) {
|
|
263
|
+
const newTodo = { id: Date.now(), text: formData.get('todo') };
|
|
264
|
+
addOptimistic(newTodo); // Instant update
|
|
265
|
+
await addTodo(formData); // Server mutation
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
return (
|
|
269
|
+
<form action={handleAdd}>
|
|
270
|
+
<input name="todo" />
|
|
271
|
+
<button type="submit">Add</button>
|
|
272
|
+
<ul>
|
|
273
|
+
{optimisticTodos.map(todo => <li key={todo.id}>{todo.text}</li>)}
|
|
274
|
+
</ul>
|
|
275
|
+
</form>
|
|
276
|
+
);
|
|
277
|
+
}
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
**Auto-reverts on error** - no manual rollback needed.
|
|
281
|
+
|
|
282
|
+
## Streaming & Suspense
|
|
283
|
+
|
|
284
|
+
### Full Page Streaming with loading.tsx
|
|
285
|
+
|
|
286
|
+
```typescript
|
|
287
|
+
// app/posts/loading.tsx
|
|
288
|
+
export default function Loading() {
|
|
289
|
+
return <PostsSkeleton />;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// app/posts/page.tsx (automatically wrapped in Suspense)
|
|
293
|
+
export default async function PostsPage() {
|
|
294
|
+
const posts = await fetchPosts(); // Streams when ready
|
|
295
|
+
return <PostsList posts={posts} />;
|
|
296
|
+
}
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
### Granular Streaming with Suspense
|
|
300
|
+
|
|
301
|
+
```typescript
|
|
302
|
+
import { Suspense } from 'react';
|
|
303
|
+
|
|
304
|
+
export default function Dashboard() {
|
|
305
|
+
return (
|
|
306
|
+
<div>
|
|
307
|
+
<h1>Dashboard</h1> {/* Sent immediately */}
|
|
308
|
+
|
|
309
|
+
<Suspense fallback={<StatsSkeleton />}>
|
|
310
|
+
<Stats /> {/* Async, streams later */}
|
|
311
|
+
</Suspense>
|
|
312
|
+
|
|
313
|
+
<Suspense fallback={<ChartSkeleton />}>
|
|
314
|
+
<RevenueChart /> {/* Async, independent stream */}
|
|
315
|
+
</Suspense>
|
|
316
|
+
</div>
|
|
317
|
+
);
|
|
318
|
+
}
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
**Benefits:**
|
|
322
|
+
- TTFB improvement - show shell instantly
|
|
323
|
+
- Independent loading states
|
|
324
|
+
- Partial prefetching for faster navigation
|
|
325
|
+
|
|
326
|
+
## Quick Reference: Framework Patterns
|
|
327
|
+
|
|
328
|
+
| Task | Next.js App Router | Remix | Vite SPA |
|
|
329
|
+
|------|-------------------|-------|----------|
|
|
330
|
+
| Data fetching | async component | loader function | useQuery |
|
|
331
|
+
| Mutations | Server Action | action function | useMutation |
|
|
332
|
+
| Loading UI | loading.tsx | useNavigation | suspense |
|
|
333
|
+
| Error UI | error.tsx | ErrorBoundary | ErrorBoundary |
|
|
334
|
+
| Metadata | metadata export | meta function | react-helmet |
|
|
335
|
+
| Navigation | Link + router | Link + navigate | Link + navigate |
|
|
336
|
+
|
|
337
|
+
## Testing Strategy
|
|
338
|
+
|
|
339
|
+
| What to Test | How |
|
|
340
|
+
|--------------|-----|
|
|
341
|
+
| Server Components | Import directly, verify data fetching |
|
|
342
|
+
| Client Components | Render with Testing Library, verify interactions |
|
|
343
|
+
| Server Actions | Call with FormData, assert return values |
|
|
344
|
+
| Suspense fallbacks | Wait for async resolution, verify both states |
|
|
345
|
+
| Forms with useActionState | Submit form, verify pending/error/success states |
|
|
346
|
+
|
|
347
|
+
```typescript
|
|
348
|
+
// Test Server Component
|
|
349
|
+
import ProductsPage from '@/app/products/page';
|
|
350
|
+
|
|
351
|
+
test('fetches and displays products', async () => {
|
|
352
|
+
const result = await ProductsPage(); // It's an async function
|
|
353
|
+
expect(result.props.children).toContain('Product 1');
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
// Test Server Action
|
|
357
|
+
import { createUser } from '@/app/actions';
|
|
358
|
+
|
|
359
|
+
test('validates email format', async () => {
|
|
360
|
+
const formData = new FormData();
|
|
361
|
+
formData.set('email', 'invalid');
|
|
362
|
+
|
|
363
|
+
const result = await createUser(null, formData);
|
|
364
|
+
expect(result.errors.email).toBeDefined();
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
// Test Client Component with Suspense
|
|
368
|
+
import { render, waitFor } from '@testing-library/react';
|
|
369
|
+
|
|
370
|
+
test('shows fallback then content', async () => {
|
|
371
|
+
const { getByText, queryByText } = render(
|
|
372
|
+
<Suspense fallback={<div>Loading...</div>}>
|
|
373
|
+
<AsyncComponent />
|
|
374
|
+
</Suspense>
|
|
375
|
+
);
|
|
376
|
+
|
|
377
|
+
expect(getByText('Loading...')).toBeInTheDocument();
|
|
378
|
+
await waitFor(() => expect(queryByText('Loading...')).not.toBeInTheDocument());
|
|
379
|
+
expect(getByText('Content')).toBeInTheDocument();
|
|
380
|
+
});
|
|
381
|
+
```
|
|
382
|
+
|
|
383
|
+
## References
|
|
384
|
+
|
|
385
|
+
- [Next.js Documentation](https://nextjs.org/docs) - App Router, Server Components, Server Actions
|
|
386
|
+
- [React 19 Release](https://react.dev/blog/2024/12/05/react-19) - use(), useActionState, useOptimistic
|
|
387
|
+
- [React Server Components](https://react.dev/reference/rsc/server-components) - Official RSC guide
|
|
388
|
+
|
|
389
|
+
**Version Notes:**
|
|
390
|
+
- Next.js 14+: App Router stable, Server Actions stable
|
|
391
|
+
- Next.js 15: Enhanced caching, Turbopack, React 19 support
|
|
392
|
+
- React 19: use(), Actions, useOptimistic, useActionState
|
|
393
|
+
|
|
394
|
+
## Red Flags - STOP and Restructure
|
|
395
|
+
|
|
396
|
+
| Thought | Reality |
|
|
397
|
+
|---------|---------|
|
|
398
|
+
| "I'll put 'use client' here just for demo" | Broken code teaches nothing. Write it correctly. |
|
|
399
|
+
| "This component does a lot but it's fine" | Max 3 concerns. Split now. |
|
|
400
|
+
| "I'll fetch client-side, it's simpler" | Server fetching is simpler AND faster. |
|
|
401
|
+
| "Let me add useState for this" | Check: can it be URL params, server state, or derived? |
|
|
402
|
+
| "Redux for this small app" | Zustand/Jotai unless you have 50+ reducers. |
|
|
403
|
+
| "I need useEffect for this fetch" | In Next.js/Remix: no. Use server components or loaders. |
|
|
404
|
+
| "I'll create an API route for this form" | Use Server Actions - no API route needed. |
|
|
405
|
+
| "Let me throw an error in Server Action" | Return error object for useActionState. Don't throw. |
|
|
406
|
+
| "I'll add 'use client' to the whole layout" | Keep boundaries deep. Only interactive parts need it. |
|
|
407
|
+
| "Forms need JavaScript to work" | Server Actions work with progressive enhancement. |
|
|
408
|
+
|
|
409
|
+
## Common Mistakes
|
|
410
|
+
|
|
411
|
+
| Mistake | Fix |
|
|
412
|
+
|---------|-----|
|
|
413
|
+
| 'use client' not at file top | Move to separate file |
|
|
414
|
+
| useEffect for data fetching in Next.js | Server component or loader |
|
|
415
|
+
| Prop drilling 3+ levels | Context or composition |
|
|
416
|
+
| Re-render on every keystroke | Debounce or uncontrolled input |
|
|
417
|
+
| window/localStorage in server component | 'use client' or dynamic import |
|
|
418
|
+
| Inline function in JSX causing re-renders | useCallback or extract |
|