@valentia-ai-skills/framework 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/README.md +103 -0
- package/bin/cli.js +482 -0
- package/package.json +42 -0
- package/scripts/postinstall.js +18 -0
- package/skills/global/api-design/SKILL.md +248 -0
- package/skills/global/api-design/tests/test-prompts.md +25 -0
- package/skills/global/code-standards/SKILL.md +245 -0
- package/skills/global/code-standards/tests/test-prompts.md +26 -0
- package/skills/global/deployment/SKILL.md +240 -0
- package/skills/global/deployment/tests/test-prompts.md +27 -0
- package/skills/global/documentation/SKILL.md +298 -0
- package/skills/global/documentation/tests/test-prompts.md +26 -0
- package/skills/global/git-workflow/SKILL.md +177 -0
- package/skills/global/git-workflow/tests/test-prompts.md +11 -0
- package/skills/global/security-baseline/SKILL.md +239 -0
- package/skills/global/security-baseline/tests/test-prompts.md +23 -0
- package/skills/global/testing-standards/SKILL.md +257 -0
- package/skills/global/testing-standards/tests/test-prompts.md +25 -0
- package/skills/onboarding/SKILL.md +110 -0
- package/skills/stack/devops/SKILL.md +220 -0
- package/skills/stack/devops/tests/test-prompts.md +29 -0
- package/skills/stack/node-backend/SKILL.md +304 -0
- package/skills/stack/node-backend/tests/test-prompts.md +27 -0
- package/skills/stack/python-backend/SKILL.md +304 -0
- package/skills/stack/python-backend/tests/test-prompts.md +27 -0
- package/skills/stack/react/SKILL.md +251 -0
- package/skills/stack/react/tests/test-prompts.md +26 -0
- package/skills/stack/react-native/SKILL.md +255 -0
- package/skills/stack/react-native/tests/test-prompts.md +26 -0
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
## Test 1: Service Creation
|
|
2
|
+
**Prompt**: "Create a user registration service that validates input, checks for duplicate emails, hashes the password, saves to DB, and sends a welcome email"
|
|
3
|
+
**Expected**:
|
|
4
|
+
- Layered: controller → service → repository
|
|
5
|
+
- Dependencies injected
|
|
6
|
+
- Zod validation at controller level
|
|
7
|
+
- Custom errors (ConflictError for duplicate, ValidationError)
|
|
8
|
+
- Password hashed with bcrypt
|
|
9
|
+
- Service doesn't touch req/res objects
|
|
10
|
+
|
|
11
|
+
## Test 2: API Setup
|
|
12
|
+
**Prompt**: "Set up an Express API with health checks, error handling, structured logging, and graceful shutdown"
|
|
13
|
+
**Expected**:
|
|
14
|
+
- /health endpoint checking DB and Redis
|
|
15
|
+
- Global error handler middleware
|
|
16
|
+
- Pino or Winston structured logging
|
|
17
|
+
- SIGTERM/SIGINT handlers with timeout
|
|
18
|
+
- Config validated at startup with Zod
|
|
19
|
+
|
|
20
|
+
## Test 3: Database Repository
|
|
21
|
+
**Prompt**: "Create a repository for an e-commerce orders table with CRUD, pagination, and filtering by status and date range"
|
|
22
|
+
**Expected**:
|
|
23
|
+
- Repository class with injected DB client
|
|
24
|
+
- Typed methods for each operation
|
|
25
|
+
- Pagination following api-design standards
|
|
26
|
+
- Transaction for create (order + order items)
|
|
27
|
+
- No business logic in repository
|
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: python-backend-patterns
|
|
3
|
+
description: >
|
|
4
|
+
Python backend development patterns for FastAPI, Django, and data services.
|
|
5
|
+
Use this skill whenever writing, reviewing, or designing Python backend code
|
|
6
|
+
including API endpoints, data pipelines, background tasks, database models,
|
|
7
|
+
or Python microservices. Triggers on: "FastAPI", "Django", "Flask", "Python API",
|
|
8
|
+
"Pydantic", "SQLAlchemy", "Alembic", "Celery", "asyncio", "uvicorn",
|
|
9
|
+
"gunicorn", "Python service", "data pipeline", "ETL", "pandas backend",
|
|
10
|
+
"Python microservice". Applies to Python backend teams.
|
|
11
|
+
version: "1.0.0"
|
|
12
|
+
scope: stack
|
|
13
|
+
stack: python-backend
|
|
14
|
+
author: Framework Admin
|
|
15
|
+
last_reviewed: 2026-03-19
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
# Python Backend Patterns
|
|
19
|
+
|
|
20
|
+
## Overview
|
|
21
|
+
|
|
22
|
+
Standardized Python backend patterns for API services and data pipelines.
|
|
23
|
+
Assumes FastAPI as the default framework (Django sections noted where applicable).
|
|
24
|
+
|
|
25
|
+
**Prerequisites**: `global/code-standards`, `global/security-baseline`, and
|
|
26
|
+
`global/api-design` all apply. This skill adds Python-specific conventions.
|
|
27
|
+
|
|
28
|
+
## 1. Project Structure (FastAPI)
|
|
29
|
+
|
|
30
|
+
```
|
|
31
|
+
src/
|
|
32
|
+
app/
|
|
33
|
+
main.py # App factory, startup/shutdown
|
|
34
|
+
config.py # Settings with Pydantic BaseSettings
|
|
35
|
+
dependencies.py # FastAPI dependency injection
|
|
36
|
+
|
|
37
|
+
routers/ # Route definitions
|
|
38
|
+
__init__.py
|
|
39
|
+
users.py
|
|
40
|
+
orders.py
|
|
41
|
+
|
|
42
|
+
services/ # Business logic
|
|
43
|
+
user_service.py
|
|
44
|
+
order_service.py
|
|
45
|
+
|
|
46
|
+
repositories/ # Database access
|
|
47
|
+
user_repository.py
|
|
48
|
+
|
|
49
|
+
models/ # SQLAlchemy models
|
|
50
|
+
user.py
|
|
51
|
+
order.py
|
|
52
|
+
|
|
53
|
+
schemas/ # Pydantic request/response schemas
|
|
54
|
+
user_schemas.py
|
|
55
|
+
order_schemas.py
|
|
56
|
+
|
|
57
|
+
middleware/ # Custom middleware
|
|
58
|
+
logging_middleware.py
|
|
59
|
+
|
|
60
|
+
exceptions/ # Custom exception classes
|
|
61
|
+
base.py
|
|
62
|
+
handlers.py
|
|
63
|
+
|
|
64
|
+
utils/ # Shared utilities
|
|
65
|
+
|
|
66
|
+
tests/
|
|
67
|
+
conftest.py # Shared fixtures
|
|
68
|
+
test_users.py
|
|
69
|
+
test_orders.py
|
|
70
|
+
|
|
71
|
+
alembic/ # Database migrations
|
|
72
|
+
versions/
|
|
73
|
+
env.py
|
|
74
|
+
|
|
75
|
+
pyproject.toml # Project config + dependencies
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### Naming Rules (Python-specific)
|
|
79
|
+
- Files and folders: `snake_case`
|
|
80
|
+
- Classes: `PascalCase`
|
|
81
|
+
- Functions and variables: `snake_case`
|
|
82
|
+
- Constants: `UPPER_SNAKE_CASE`
|
|
83
|
+
- Private methods/attrs: `_single_underscore`
|
|
84
|
+
- Follow PEP 8 — enforced via ruff
|
|
85
|
+
|
|
86
|
+
## 2. Configuration
|
|
87
|
+
|
|
88
|
+
```python
|
|
89
|
+
# config.py
|
|
90
|
+
from pydantic_settings import BaseSettings
|
|
91
|
+
|
|
92
|
+
class Settings(BaseSettings):
|
|
93
|
+
"""Application settings — loaded from environment variables."""
|
|
94
|
+
|
|
95
|
+
app_name: str = "my-service"
|
|
96
|
+
environment: str = "development"
|
|
97
|
+
debug: bool = False
|
|
98
|
+
|
|
99
|
+
# Database
|
|
100
|
+
database_url: str
|
|
101
|
+
database_pool_size: int = 5
|
|
102
|
+
|
|
103
|
+
# Redis
|
|
104
|
+
redis_url: str
|
|
105
|
+
|
|
106
|
+
# Auth
|
|
107
|
+
jwt_secret: str
|
|
108
|
+
jwt_expiry_minutes: int = 15
|
|
109
|
+
|
|
110
|
+
# External services
|
|
111
|
+
email_api_key: str = ""
|
|
112
|
+
|
|
113
|
+
class Config:
|
|
114
|
+
env_file = ".env"
|
|
115
|
+
|
|
116
|
+
settings = Settings()
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### Rules
|
|
120
|
+
- All config through Pydantic `BaseSettings` — validates at startup
|
|
121
|
+
- Never use `os.getenv()` directly in service code
|
|
122
|
+
- Type all settings — Pydantic will fail fast on invalid values
|
|
123
|
+
- Secrets loaded from env vars, never hardcoded
|
|
124
|
+
|
|
125
|
+
## 3. Request/Response Schemas
|
|
126
|
+
|
|
127
|
+
```python
|
|
128
|
+
# schemas/user_schemas.py
|
|
129
|
+
from pydantic import BaseModel, EmailStr, Field
|
|
130
|
+
from datetime import datetime
|
|
131
|
+
|
|
132
|
+
class CreateUserRequest(BaseModel):
|
|
133
|
+
email: EmailStr
|
|
134
|
+
name: str = Field(min_length=1, max_length=100)
|
|
135
|
+
role: str = Field(pattern="^(user|admin|editor)$")
|
|
136
|
+
|
|
137
|
+
class UserResponse(BaseModel):
|
|
138
|
+
id: str
|
|
139
|
+
email: str
|
|
140
|
+
name: str
|
|
141
|
+
role: str
|
|
142
|
+
created_at: datetime
|
|
143
|
+
|
|
144
|
+
class Config:
|
|
145
|
+
from_attributes = True # allows ORM objects
|
|
146
|
+
|
|
147
|
+
class UserListResponse(BaseModel):
|
|
148
|
+
data: list[UserResponse]
|
|
149
|
+
pagination: PaginationResponse
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
### Rules
|
|
153
|
+
- Separate schemas for request (input) and response (output)
|
|
154
|
+
- Use Pydantic `Field` for validation rules
|
|
155
|
+
- Response schemas use `from_attributes = True` for ORM compatibility
|
|
156
|
+
- Follow the `data` / `error` envelope from api-design standards
|
|
157
|
+
|
|
158
|
+
## 4. Dependency Injection (FastAPI)
|
|
159
|
+
|
|
160
|
+
```python
|
|
161
|
+
# dependencies.py
|
|
162
|
+
from fastapi import Depends
|
|
163
|
+
from sqlalchemy.ext.asyncio import AsyncSession
|
|
164
|
+
|
|
165
|
+
async def get_db() -> AsyncGenerator[AsyncSession, None]:
|
|
166
|
+
async with async_session_maker() as session:
|
|
167
|
+
yield session
|
|
168
|
+
|
|
169
|
+
async def get_user_service(
|
|
170
|
+
db: AsyncSession = Depends(get_db),
|
|
171
|
+
) -> UserService:
|
|
172
|
+
repo = UserRepository(db)
|
|
173
|
+
return UserService(repo)
|
|
174
|
+
|
|
175
|
+
# In routers:
|
|
176
|
+
@router.post("/users", status_code=201)
|
|
177
|
+
async def create_user(
|
|
178
|
+
data: CreateUserRequest,
|
|
179
|
+
service: UserService = Depends(get_user_service),
|
|
180
|
+
) -> UserResponse:
|
|
181
|
+
return await service.create_user(data)
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
### Rules
|
|
185
|
+
- Use FastAPI's `Depends()` for all dependency injection
|
|
186
|
+
- Database sessions via `yield` dependency (ensures cleanup)
|
|
187
|
+
- Services receive repositories through DI — never import directly
|
|
188
|
+
- All dependencies are explicit and testable
|
|
189
|
+
|
|
190
|
+
## 5. Async/Await Rules
|
|
191
|
+
|
|
192
|
+
```python
|
|
193
|
+
# ✅ Good: async for I/O operations
|
|
194
|
+
async def get_user(user_id: str) -> User:
|
|
195
|
+
return await db.execute(select(User).where(User.id == user_id))
|
|
196
|
+
|
|
197
|
+
# ✅ Good: sync for CPU-bound operations
|
|
198
|
+
def calculate_report(data: list[dict]) -> Report:
|
|
199
|
+
return Report(totals=sum(d["amount"] for d in data))
|
|
200
|
+
|
|
201
|
+
# ❌ Bad: blocking I/O in async function
|
|
202
|
+
async def get_user(user_id: str) -> User:
|
|
203
|
+
return requests.get(f"/api/users/{user_id}") # blocks the event loop!
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
### Rules
|
|
207
|
+
- Use `async/await` for all I/O (database, HTTP, file system)
|
|
208
|
+
- Use `httpx` (async) instead of `requests` (sync) for HTTP calls
|
|
209
|
+
- Use `asyncpg` or async SQLAlchemy for database access
|
|
210
|
+
- Never mix sync blocking calls in async code paths
|
|
211
|
+
- For CPU-heavy work in async context, use `asyncio.to_thread()`
|
|
212
|
+
|
|
213
|
+
## 6. Error Handling
|
|
214
|
+
|
|
215
|
+
```python
|
|
216
|
+
# exceptions/base.py
|
|
217
|
+
class AppError(Exception):
|
|
218
|
+
def __init__(self, message: str, status_code: int, code: str, details=None):
|
|
219
|
+
self.message = message
|
|
220
|
+
self.status_code = status_code
|
|
221
|
+
self.code = code
|
|
222
|
+
self.details = details
|
|
223
|
+
super().__init__(message)
|
|
224
|
+
|
|
225
|
+
class NotFoundError(AppError):
|
|
226
|
+
def __init__(self, resource: str, resource_id: str):
|
|
227
|
+
super().__init__(
|
|
228
|
+
message=f"{resource} with id {resource_id} not found",
|
|
229
|
+
status_code=404,
|
|
230
|
+
code="NOT_FOUND",
|
|
231
|
+
)
|
|
232
|
+
|
|
233
|
+
class ValidationError(AppError):
|
|
234
|
+
def __init__(self, details: list[dict]):
|
|
235
|
+
super().__init__(
|
|
236
|
+
message="Validation failed",
|
|
237
|
+
status_code=400,
|
|
238
|
+
code="VALIDATION_ERROR",
|
|
239
|
+
details=details,
|
|
240
|
+
)
|
|
241
|
+
|
|
242
|
+
# exceptions/handlers.py
|
|
243
|
+
@app.exception_handler(AppError)
|
|
244
|
+
async def app_error_handler(request: Request, exc: AppError):
|
|
245
|
+
return JSONResponse(
|
|
246
|
+
status_code=exc.status_code,
|
|
247
|
+
content={"error": {"code": exc.code, "message": exc.message, "details": exc.details}},
|
|
248
|
+
)
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
## 7. Testing (pytest)
|
|
252
|
+
|
|
253
|
+
```python
|
|
254
|
+
# conftest.py
|
|
255
|
+
import pytest
|
|
256
|
+
from httpx import AsyncClient
|
|
257
|
+
|
|
258
|
+
@pytest.fixture
|
|
259
|
+
async def db_session():
|
|
260
|
+
async with test_engine.begin() as conn:
|
|
261
|
+
await conn.run_sync(Base.metadata.create_all)
|
|
262
|
+
async with async_test_session() as session:
|
|
263
|
+
yield session
|
|
264
|
+
async with test_engine.begin() as conn:
|
|
265
|
+
await conn.run_sync(Base.metadata.drop_all)
|
|
266
|
+
|
|
267
|
+
@pytest.fixture
|
|
268
|
+
async def client(db_session):
|
|
269
|
+
app.dependency_overrides[get_db] = lambda: db_session
|
|
270
|
+
async with AsyncClient(app=app, base_url="http://test") as ac:
|
|
271
|
+
yield ac
|
|
272
|
+
app.dependency_overrides.clear()
|
|
273
|
+
|
|
274
|
+
# test_users.py
|
|
275
|
+
@pytest.mark.asyncio
|
|
276
|
+
async def test_create_user_success(client: AsyncClient):
|
|
277
|
+
response = await client.post("/api/v1/users", json={
|
|
278
|
+
"email": "alice@example.com",
|
|
279
|
+
"name": "Alice",
|
|
280
|
+
"role": "user",
|
|
281
|
+
})
|
|
282
|
+
assert response.status_code == 201
|
|
283
|
+
assert response.json()["data"]["email"] == "alice@example.com"
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
### Rules
|
|
287
|
+
- Use `pytest` with `pytest-asyncio` for async tests
|
|
288
|
+
- Override FastAPI dependencies in fixtures (not mocking)
|
|
289
|
+
- Use `httpx.AsyncClient` for integration tests
|
|
290
|
+
- Factory fixtures for test data (see testing-standards skill)
|
|
291
|
+
|
|
292
|
+
## Checklist
|
|
293
|
+
|
|
294
|
+
Before finalizing Python backend code:
|
|
295
|
+
- [ ] Layered: routers → services → repositories
|
|
296
|
+
- [ ] Config via Pydantic BaseSettings, validated at startup
|
|
297
|
+
- [ ] Request/response schemas separated (Pydantic)
|
|
298
|
+
- [ ] Dependencies injected via FastAPI Depends()
|
|
299
|
+
- [ ] All I/O is async (httpx, async SQLAlchemy)
|
|
300
|
+
- [ ] Custom error classes with status codes
|
|
301
|
+
- [ ] Global exception handler returns standard error envelope
|
|
302
|
+
- [ ] Migrations via Alembic (not manual SQL)
|
|
303
|
+
- [ ] Tests use dependency override, not mocking
|
|
304
|
+
- [ ] ruff for linting, mypy for type checking
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
## Test 1: FastAPI Service
|
|
2
|
+
**Prompt**: "Create a FastAPI service for managing a product catalog with CRUD endpoints, Pydantic schemas, and SQLAlchemy models"
|
|
3
|
+
**Expected**:
|
|
4
|
+
- Project structure follows the standard layout
|
|
5
|
+
- Separate request/response schemas
|
|
6
|
+
- Repository pattern for DB access
|
|
7
|
+
- Dependency injection via Depends()
|
|
8
|
+
- Async database operations
|
|
9
|
+
- Custom error handling
|
|
10
|
+
|
|
11
|
+
## Test 2: Configuration & Startup
|
|
12
|
+
**Prompt**: "Set up the application config, database connection, and health check for a FastAPI service"
|
|
13
|
+
**Expected**:
|
|
14
|
+
- Pydantic BaseSettings for config
|
|
15
|
+
- Async SQLAlchemy session factory
|
|
16
|
+
- /health endpoint checking DB
|
|
17
|
+
- Startup/shutdown lifecycle events
|
|
18
|
+
- Graceful error on missing env vars
|
|
19
|
+
|
|
20
|
+
## Test 3: Background Task
|
|
21
|
+
**Prompt**: "Create an endpoint that accepts a CSV upload, processes it in the background, and returns the job status"
|
|
22
|
+
**Expected**:
|
|
23
|
+
- Upload endpoint with file validation
|
|
24
|
+
- Background task (Celery or FastAPI BackgroundTasks)
|
|
25
|
+
- Job status tracking
|
|
26
|
+
- Async file processing
|
|
27
|
+
- Error handling for bad CSV data
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: react-patterns
|
|
3
|
+
description: >
|
|
4
|
+
React development patterns and conventions for web teams. Use this skill
|
|
5
|
+
whenever writing, reviewing, or refactoring React components, hooks,
|
|
6
|
+
state management, context, effects, forms, routing, or any React-specific
|
|
7
|
+
code. Triggers on: "React", "component", "hook", "useState", "useEffect",
|
|
8
|
+
"useRef", "useContext", "useMemo", "useCallback", "JSX", "TSX", "props",
|
|
9
|
+
"state", "context", "reducer", "render", "virtual DOM", "Next.js", "Remix",
|
|
10
|
+
"React Router", "form handling", "controlled component", "uncontrolled".
|
|
11
|
+
Applies to all web teams using React.
|
|
12
|
+
version: "1.0.0"
|
|
13
|
+
scope: stack
|
|
14
|
+
stack: react
|
|
15
|
+
author: Framework Admin
|
|
16
|
+
last_reviewed: 2026-03-19
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
# React Patterns
|
|
20
|
+
|
|
21
|
+
## Overview
|
|
22
|
+
|
|
23
|
+
Standardized React patterns ensure consistent component architecture across
|
|
24
|
+
all web teams. These conventions cover component structure, hooks usage,
|
|
25
|
+
state management, and performance patterns.
|
|
26
|
+
|
|
27
|
+
## 1. Component Structure
|
|
28
|
+
|
|
29
|
+
### File Template
|
|
30
|
+
Every component file follows this order:
|
|
31
|
+
|
|
32
|
+
```tsx
|
|
33
|
+
// 1. Imports (follow code-standards import order)
|
|
34
|
+
import { useState, useCallback } from "react";
|
|
35
|
+
import { Button } from "@/components/ui/button";
|
|
36
|
+
import type { User } from "@/types/user";
|
|
37
|
+
|
|
38
|
+
// 2. Types / Interfaces
|
|
39
|
+
interface UserCardProps {
|
|
40
|
+
user: User;
|
|
41
|
+
onEdit: (userId: string) => void;
|
|
42
|
+
isCompact?: boolean;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// 3. Constants (component-specific)
|
|
46
|
+
const MAX_BIO_LENGTH = 200;
|
|
47
|
+
|
|
48
|
+
// 4. Component
|
|
49
|
+
export function UserCard({ user, onEdit, isCompact = false }: UserCardProps) {
|
|
50
|
+
// 4a. Hooks (always at the top, same order every time)
|
|
51
|
+
const [isExpanded, setIsExpanded] = useState(false);
|
|
52
|
+
|
|
53
|
+
// 4b. Derived state (computed from props/state)
|
|
54
|
+
const displayName = `${user.firstName} ${user.lastName}`;
|
|
55
|
+
const truncatedBio = user.bio?.slice(0, MAX_BIO_LENGTH);
|
|
56
|
+
|
|
57
|
+
// 4c. Handlers
|
|
58
|
+
const handleEditClick = useCallback(() => {
|
|
59
|
+
onEdit(user.id);
|
|
60
|
+
}, [onEdit, user.id]);
|
|
61
|
+
|
|
62
|
+
// 4d. Early returns (loading, error, empty states)
|
|
63
|
+
if (!user) return null;
|
|
64
|
+
|
|
65
|
+
// 4e. Render
|
|
66
|
+
return (
|
|
67
|
+
<div className={isCompact ? "p-2" : "p-4"}>
|
|
68
|
+
<h3>{displayName}</h3>
|
|
69
|
+
<p>{isExpanded ? user.bio : truncatedBio}</p>
|
|
70
|
+
<Button onClick={handleEditClick}>Edit</Button>
|
|
71
|
+
</div>
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### Rules
|
|
77
|
+
- One component per file (with small sub-components allowed)
|
|
78
|
+
- Named exports for components (not default, except page-level)
|
|
79
|
+
- Component name matches file name: `user-card.tsx` → `UserCard`
|
|
80
|
+
- Props interface defined above the component
|
|
81
|
+
- All props typed — no `any`
|
|
82
|
+
|
|
83
|
+
## 2. Hooks
|
|
84
|
+
|
|
85
|
+
### Hook Order (always consistent)
|
|
86
|
+
```tsx
|
|
87
|
+
function MyComponent() {
|
|
88
|
+
// 1. External hooks (router, context, etc.)
|
|
89
|
+
const router = useRouter();
|
|
90
|
+
const { user } = useAuth();
|
|
91
|
+
|
|
92
|
+
// 2. State hooks
|
|
93
|
+
const [count, setCount] = useState(0);
|
|
94
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
95
|
+
|
|
96
|
+
// 3. Refs
|
|
97
|
+
const inputRef = useRef<HTMLInputElement>(null);
|
|
98
|
+
|
|
99
|
+
// 4. Derived state (useMemo for expensive computations only)
|
|
100
|
+
const total = useMemo(() => calculateTotal(items), [items]);
|
|
101
|
+
|
|
102
|
+
// 5. Effects
|
|
103
|
+
useEffect(() => { ... }, [dependency]);
|
|
104
|
+
|
|
105
|
+
// 6. Callbacks
|
|
106
|
+
const handleSubmit = useCallback(() => { ... }, [deps]);
|
|
107
|
+
}
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### Custom Hook Rules
|
|
111
|
+
- Prefix with `use`: `useAuth`, `useDebounce`, `usePagination`
|
|
112
|
+
- Extract when logic is reused across 2+ components
|
|
113
|
+
- Put in `hooks/` directory: `hooks/use-debounce.ts`
|
|
114
|
+
- Always return a typed value
|
|
115
|
+
|
|
116
|
+
```tsx
|
|
117
|
+
// ✅ Good: Clean custom hook
|
|
118
|
+
function useDebounce<T>(value: T, delayMs: number): T {
|
|
119
|
+
const [debouncedValue, setDebouncedValue] = useState(value);
|
|
120
|
+
|
|
121
|
+
useEffect(() => {
|
|
122
|
+
const timer = setTimeout(() => setDebouncedValue(value), delayMs);
|
|
123
|
+
return () => clearTimeout(timer);
|
|
124
|
+
}, [value, delayMs]);
|
|
125
|
+
|
|
126
|
+
return debouncedValue;
|
|
127
|
+
}
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### useEffect Rules
|
|
131
|
+
- Every effect must have a dependency array
|
|
132
|
+
- Empty `[]` only for mount-once effects (and add a comment explaining why)
|
|
133
|
+
- Clean up subscriptions, timers, and listeners in the return function
|
|
134
|
+
- Never lie about dependencies — if ESLint warns, fix the code, don't suppress
|
|
135
|
+
|
|
136
|
+
✅ Good:
|
|
137
|
+
```tsx
|
|
138
|
+
useEffect(() => {
|
|
139
|
+
const controller = new AbortController();
|
|
140
|
+
|
|
141
|
+
fetchUser(userId, { signal: controller.signal })
|
|
142
|
+
.then(setUser)
|
|
143
|
+
.catch(err => {
|
|
144
|
+
if (err.name !== "AbortError") setError(err);
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
return () => controller.abort(); // cleanup
|
|
148
|
+
}, [userId]);
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
❌ Bad:
|
|
152
|
+
```tsx
|
|
153
|
+
useEffect(() => {
|
|
154
|
+
fetchUser(userId).then(setUser);
|
|
155
|
+
}); // missing dependency array — runs every render
|
|
156
|
+
|
|
157
|
+
useEffect(() => {
|
|
158
|
+
fetchUser(userId).then(setUser);
|
|
159
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
160
|
+
}, []); // suppressing the warning instead of fixing
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
## 3. State Management
|
|
164
|
+
|
|
165
|
+
### Decision Matrix
|
|
166
|
+
| Scope | Solution |
|
|
167
|
+
|-------|----------|
|
|
168
|
+
| Single component | `useState` |
|
|
169
|
+
| Parent → Children (2-3 levels) | Props drilling (it's fine!) |
|
|
170
|
+
| Deep tree (4+ levels) | React Context |
|
|
171
|
+
| Complex state transitions | `useReducer` |
|
|
172
|
+
| Server state (API data) | TanStack Query / SWR |
|
|
173
|
+
| Global client state | Zustand (preferred) or Redux Toolkit |
|
|
174
|
+
|
|
175
|
+
### Rules
|
|
176
|
+
- Start simple — don't add Zustand/Redux until you actually need it
|
|
177
|
+
- Server state belongs in TanStack Query, not in global state
|
|
178
|
+
- Context is for slow-changing data (theme, auth, locale) — not for frequently updating state
|
|
179
|
+
- Never put derived state in useState — compute it inline
|
|
180
|
+
|
|
181
|
+
```tsx
|
|
182
|
+
// ❌ Bad: Storing derived state
|
|
183
|
+
const [fullName, setFullName] = useState(`${first} ${last}`);
|
|
184
|
+
useEffect(() => setFullName(`${first} ${last}`), [first, last]);
|
|
185
|
+
|
|
186
|
+
// ✅ Good: Compute inline
|
|
187
|
+
const fullName = `${first} ${last}`;
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
## 4. Event Handling
|
|
191
|
+
|
|
192
|
+
- Handlers prefixed with `handle`: `handleClick`, `handleSubmit`
|
|
193
|
+
- Callback props prefixed with `on`: `onClick`, `onSubmit`
|
|
194
|
+
- Use `useCallback` only when passing handlers to memoized children
|
|
195
|
+
|
|
196
|
+
```tsx
|
|
197
|
+
// In parent:
|
|
198
|
+
const handleUserSelect = useCallback((userId: string) => {
|
|
199
|
+
setSelectedUser(userId);
|
|
200
|
+
}, []);
|
|
201
|
+
|
|
202
|
+
// In child props:
|
|
203
|
+
<UserList onSelect={handleUserSelect} />
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
## 5. Conditional Rendering
|
|
207
|
+
|
|
208
|
+
```tsx
|
|
209
|
+
// Boolean: use &&
|
|
210
|
+
{isLoggedIn && <Dashboard />}
|
|
211
|
+
|
|
212
|
+
// Binary: use ternary
|
|
213
|
+
{isLoading ? <Spinner /> : <Content />}
|
|
214
|
+
|
|
215
|
+
// Multiple conditions: use early return or extracted function
|
|
216
|
+
function StatusBadge({ status }: { status: OrderStatus }) {
|
|
217
|
+
if (status === "pending") return <Badge color="yellow">Pending</Badge>;
|
|
218
|
+
if (status === "shipped") return <Badge color="blue">Shipped</Badge>;
|
|
219
|
+
if (status === "delivered") return <Badge color="green">Delivered</Badge>;
|
|
220
|
+
return null;
|
|
221
|
+
}
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
### Watch Out
|
|
225
|
+
```tsx
|
|
226
|
+
// ❌ Bug: renders "0" when count is 0
|
|
227
|
+
{count && <Tag>{count}</Tag>}
|
|
228
|
+
|
|
229
|
+
// ✅ Fix: explicit boolean check
|
|
230
|
+
{count > 0 && <Tag>{count}</Tag>}
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
## 6. Performance
|
|
234
|
+
|
|
235
|
+
- Use `React.memo` only for components that re-render with same props frequently
|
|
236
|
+
- Use `useMemo` only for genuinely expensive computations (not simple lookups)
|
|
237
|
+
- Use `useCallback` only when passing to memoized children
|
|
238
|
+
- Profile first, optimize second — don't premature-optimize
|
|
239
|
+
- Lazy load heavy components: `const HeavyChart = React.lazy(() => import("./heavy-chart"))`
|
|
240
|
+
|
|
241
|
+
## Checklist
|
|
242
|
+
|
|
243
|
+
Before finalizing React code:
|
|
244
|
+
- [ ] One component per file, named export, props typed
|
|
245
|
+
- [ ] Hooks in consistent order (context → state → refs → memo → effects → callbacks)
|
|
246
|
+
- [ ] All useEffect have dependency arrays and cleanup functions
|
|
247
|
+
- [ ] No derived state in useState — computed inline
|
|
248
|
+
- [ ] Event handlers: handle* (internal) and on* (props)
|
|
249
|
+
- [ ] State management matches scope (don't over-engineer)
|
|
250
|
+
- [ ] No eslint-disable for hook dependency warnings
|
|
251
|
+
- [ ] Loading, error, and empty states handled
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
## Test 1: Component Creation
|
|
2
|
+
**Prompt**: "Build a product card component that shows image, title, price, rating, and an add-to-cart button with quantity selector"
|
|
3
|
+
**Expected**:
|
|
4
|
+
- Props interface with all fields typed
|
|
5
|
+
- Named export
|
|
6
|
+
- Hooks in correct order
|
|
7
|
+
- Handlers prefixed with handle
|
|
8
|
+
- Loading/empty state handled
|
|
9
|
+
|
|
10
|
+
## Test 2: Data Fetching Component
|
|
11
|
+
**Prompt**: "Create a user profile page that fetches user data from an API, shows loading state, error state, and allows editing the bio field"
|
|
12
|
+
**Expected**:
|
|
13
|
+
- useEffect with proper cleanup (AbortController)
|
|
14
|
+
- Dependency array correct
|
|
15
|
+
- Loading, error, and success states
|
|
16
|
+
- No derived state in useState
|
|
17
|
+
- Form handling with controlled input
|
|
18
|
+
|
|
19
|
+
## Test 3: State Management Decision
|
|
20
|
+
**Prompt**: "I have a shopping cart that needs to be accessible from the navbar, product pages, and checkout. What state management should I use?"
|
|
21
|
+
**Expected**:
|
|
22
|
+
- Recommends Zustand or Context based on complexity
|
|
23
|
+
- Explains why not just props (too many levels)
|
|
24
|
+
- Server state (product data) in TanStack Query
|
|
25
|
+
- Client state (cart items) in Zustand/Context
|
|
26
|
+
- Shows code example
|