mirror of
https://github.com/duthaho/claudekit.git
synced 2026-06-15 14:34:55 +03:00
feat: add verification methodology and writing plans skills
This commit is contained in:
@@ -0,0 +1,245 @@
|
||||
---
|
||||
title: Django
|
||||
description: Django ORM, views, and REST framework patterns
|
||||
---
|
||||
|
||||
The Django skill provides expertise in Django web framework including ORM, class-based views, and Django REST Framework.
|
||||
|
||||
## When Activated
|
||||
|
||||
- Python web applications
|
||||
- Admin interfaces
|
||||
- Django REST Framework APIs
|
||||
- Working with Django projects
|
||||
|
||||
## Core Patterns
|
||||
|
||||
### Models
|
||||
|
||||
```python
|
||||
from django.db import models
|
||||
|
||||
class User(models.Model):
|
||||
email = models.EmailField(unique=True)
|
||||
name = models.CharField(max_length=100)
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
updated_at = models.DateTimeField(auto_now=True)
|
||||
|
||||
class Meta:
|
||||
ordering = ['-created_at']
|
||||
indexes = [
|
||||
models.Index(fields=['email']),
|
||||
]
|
||||
|
||||
def __str__(self):
|
||||
return self.email
|
||||
```
|
||||
|
||||
### Views (Class-based)
|
||||
|
||||
```python
|
||||
from django.views.generic import ListView, DetailView, CreateView
|
||||
|
||||
class UserListView(ListView):
|
||||
model = User
|
||||
template_name = 'users/list.html'
|
||||
context_object_name = 'users'
|
||||
paginate_by = 20
|
||||
|
||||
class UserDetailView(DetailView):
|
||||
model = User
|
||||
template_name = 'users/detail.html'
|
||||
context_object_name = 'user'
|
||||
|
||||
class UserCreateView(CreateView):
|
||||
model = User
|
||||
fields = ['email', 'name']
|
||||
template_name = 'users/create.html'
|
||||
success_url = '/users/'
|
||||
```
|
||||
|
||||
### Django REST Framework
|
||||
|
||||
```python
|
||||
from rest_framework import serializers, viewsets
|
||||
from rest_framework.decorators import action
|
||||
from rest_framework.response import Response
|
||||
|
||||
class UserSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = User
|
||||
fields = ['id', 'email', 'name', 'created_at']
|
||||
read_only_fields = ['created_at']
|
||||
|
||||
class UserViewSet(viewsets.ModelViewSet):
|
||||
queryset = User.objects.all()
|
||||
serializer_class = UserSerializer
|
||||
|
||||
@action(detail=True, methods=['post'])
|
||||
def activate(self, request, pk=None):
|
||||
user = self.get_object()
|
||||
user.is_active = True
|
||||
user.save()
|
||||
return Response({'status': 'activated'})
|
||||
```
|
||||
|
||||
### URLs
|
||||
|
||||
```python
|
||||
from django.urls import path, include
|
||||
from rest_framework.routers import DefaultRouter
|
||||
|
||||
router = DefaultRouter()
|
||||
router.register('users', UserViewSet)
|
||||
|
||||
urlpatterns = [
|
||||
path('api/', include(router.urls)),
|
||||
path('users/', UserListView.as_view(), name='user-list'),
|
||||
path('users/<int:pk>/', UserDetailView.as_view(), name='user-detail'),
|
||||
]
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Use class-based views for standard CRUD**
|
||||
2. **Define model methods for business logic**
|
||||
3. **Use serializers for validation**
|
||||
4. **Add proper permissions**
|
||||
5. **Use select_related/prefetch_related for queries**
|
||||
|
||||
## Common Pitfalls
|
||||
|
||||
### N+1 Queries
|
||||
|
||||
```python
|
||||
# ❌ BAD: N+1 query problem
|
||||
users = User.objects.all()
|
||||
for user in users:
|
||||
print(user.profile.bio) # Separate query for each user
|
||||
|
||||
# ✅ GOOD: Use select_related
|
||||
users = User.objects.select_related('profile').all()
|
||||
for user in users:
|
||||
print(user.profile.bio) # Single query
|
||||
```
|
||||
|
||||
### Missing Migrations
|
||||
|
||||
```python
|
||||
# ❌ BAD: Forgetting migrations
|
||||
# After changing models, forgetting to:
|
||||
python manage.py makemigrations
|
||||
python manage.py migrate
|
||||
|
||||
# ✅ GOOD: Always create and run migrations
|
||||
python manage.py makemigrations
|
||||
python manage.py migrate
|
||||
```
|
||||
|
||||
### No Validation
|
||||
|
||||
```python
|
||||
# ❌ BAD: Direct model creation
|
||||
user = User.objects.create(email=request.POST['email'])
|
||||
|
||||
# ✅ GOOD: Use serializer for validation
|
||||
serializer = UserSerializer(data=request.data)
|
||||
if serializer.is_valid():
|
||||
user = serializer.save()
|
||||
else:
|
||||
return Response(serializer.errors, status=400)
|
||||
```
|
||||
|
||||
## Advanced Patterns
|
||||
|
||||
### Custom Managers
|
||||
|
||||
```python
|
||||
class ActiveUserManager(models.Manager):
|
||||
def get_queryset(self):
|
||||
return super().get_queryset().filter(is_active=True)
|
||||
|
||||
class User(models.Model):
|
||||
# ...
|
||||
objects = models.Manager()
|
||||
active = ActiveUserManager()
|
||||
|
||||
# Usage
|
||||
all_users = User.objects.all()
|
||||
active_users = User.active.all()
|
||||
```
|
||||
|
||||
### Signals
|
||||
|
||||
```python
|
||||
from django.db.models.signals import post_save
|
||||
from django.dispatch import receiver
|
||||
|
||||
@receiver(post_save, sender=User)
|
||||
def create_user_profile(sender, instance, created, **kwargs):
|
||||
if created:
|
||||
Profile.objects.create(user=instance)
|
||||
```
|
||||
|
||||
### Permissions
|
||||
|
||||
```python
|
||||
from rest_framework import permissions
|
||||
|
||||
class IsOwnerOrReadOnly(permissions.BasePermission):
|
||||
def has_object_permission(self, request, view, obj):
|
||||
if request.method in permissions.SAFE_METHODS:
|
||||
return True
|
||||
return obj.owner == request.user
|
||||
|
||||
class UserViewSet(viewsets.ModelViewSet):
|
||||
permission_classes = [IsOwnerOrReadOnly]
|
||||
# ...
|
||||
```
|
||||
|
||||
### Pagination
|
||||
|
||||
```python
|
||||
from rest_framework.pagination import PageNumberPagination
|
||||
|
||||
class StandardResultsSetPagination(PageNumberPagination):
|
||||
page_size = 20
|
||||
page_size_query_param = 'page_size'
|
||||
max_page_size = 100
|
||||
|
||||
class UserViewSet(viewsets.ModelViewSet):
|
||||
pagination_class = StandardResultsSetPagination
|
||||
# ...
|
||||
```
|
||||
|
||||
## Testing
|
||||
|
||||
```python
|
||||
from django.test import TestCase
|
||||
from rest_framework.test import APITestCase
|
||||
|
||||
class UserModelTest(TestCase):
|
||||
def test_create_user(self):
|
||||
user = User.objects.create(
|
||||
email='test@example.com',
|
||||
name='Test User'
|
||||
)
|
||||
self.assertEqual(user.email, 'test@example.com')
|
||||
|
||||
class UserAPITest(APITestCase):
|
||||
def test_list_users(self):
|
||||
response = self.client.get('/api/users/')
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
def test_create_user(self):
|
||||
data = {'email': 'new@example.com', 'name': 'New User'}
|
||||
response = self.client.post('/api/users/', data)
|
||||
self.assertEqual(response.status_code, 201)
|
||||
```
|
||||
|
||||
## Related Skills
|
||||
|
||||
- [Python](/claudekit/skills/languages/python) - Python language
|
||||
- [PostgreSQL](/claudekit/skills/databases/postgresql) - Database
|
||||
- [pytest](/claudekit/skills/testing/pytest) - Testing
|
||||
- [OpenAPI](/claudekit/skills/api/openapi) - API documentation
|
||||
@@ -0,0 +1,283 @@
|
||||
---
|
||||
title: FastAPI
|
||||
description: FastAPI async patterns, Pydantic validation, and OpenAPI documentation
|
||||
---
|
||||
|
||||
The FastAPI skill provides expertise in building REST APIs with Python, async patterns, Pydantic validation, and automatic OpenAPI documentation.
|
||||
|
||||
## When Activated
|
||||
|
||||
- Building REST APIs with Python
|
||||
- Async web applications
|
||||
- OpenAPI/Swagger documentation needed
|
||||
- Working with FastAPI framework
|
||||
|
||||
## Core Patterns
|
||||
|
||||
### Route Definition
|
||||
|
||||
```python
|
||||
from fastapi import FastAPI, HTTPException, Depends
|
||||
from pydantic import BaseModel
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
class UserCreate(BaseModel):
|
||||
email: str
|
||||
name: str
|
||||
|
||||
class UserResponse(BaseModel):
|
||||
id: int
|
||||
email: str
|
||||
name: str
|
||||
|
||||
@app.post("/users", response_model=UserResponse, status_code=201)
|
||||
async def create_user(user: UserCreate):
|
||||
# Create user logic
|
||||
return UserResponse(id=1, **user.model_dump())
|
||||
|
||||
@app.get("/users/{user_id}", response_model=UserResponse)
|
||||
async def get_user(user_id: int):
|
||||
user = await get_user_by_id(user_id)
|
||||
if not user:
|
||||
raise HTTPException(status_code=404, detail="User not found")
|
||||
return user
|
||||
```
|
||||
|
||||
### Dependency Injection
|
||||
|
||||
```python
|
||||
from fastapi import Depends
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
async def get_db() -> AsyncGenerator[AsyncSession, None]:
|
||||
async with async_session_maker() as session:
|
||||
yield session
|
||||
|
||||
@app.get("/users")
|
||||
async def list_users(db: AsyncSession = Depends(get_db)):
|
||||
result = await db.execute(select(User))
|
||||
return result.scalars().all()
|
||||
```
|
||||
|
||||
### Router Organization
|
||||
|
||||
```python
|
||||
from fastapi import APIRouter
|
||||
|
||||
router = APIRouter(prefix="/users", tags=["users"])
|
||||
|
||||
@router.get("/")
|
||||
async def list_users():
|
||||
pass
|
||||
|
||||
@router.post("/")
|
||||
async def create_user(user: UserCreate):
|
||||
pass
|
||||
|
||||
# In main.py
|
||||
app.include_router(router)
|
||||
```
|
||||
|
||||
### Request Validation
|
||||
|
||||
```python
|
||||
from pydantic import BaseModel, EmailStr, Field
|
||||
|
||||
class UserCreate(BaseModel):
|
||||
email: EmailStr
|
||||
name: str = Field(min_length=1, max_length=100)
|
||||
age: int = Field(ge=0, le=150)
|
||||
|
||||
class Config:
|
||||
json_schema_extra = {
|
||||
"example": {
|
||||
"email": "user@example.com",
|
||||
"name": "John Doe",
|
||||
"age": 30
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Error Handling
|
||||
|
||||
```python
|
||||
from fastapi import HTTPException, status
|
||||
from fastapi.responses import JSONResponse
|
||||
|
||||
class UserNotFoundError(Exception):
|
||||
pass
|
||||
|
||||
@app.exception_handler(UserNotFoundError)
|
||||
async def user_not_found_handler(request, exc):
|
||||
return JSONResponse(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
content={"detail": "User not found"}
|
||||
)
|
||||
|
||||
@app.get("/users/{user_id}")
|
||||
async def get_user(user_id: int):
|
||||
user = await find_user(user_id)
|
||||
if not user:
|
||||
raise UserNotFoundError()
|
||||
return user
|
||||
```
|
||||
|
||||
### Background Tasks
|
||||
|
||||
```python
|
||||
from fastapi import BackgroundTasks
|
||||
|
||||
def send_email(email: str, message: str):
|
||||
# Send email logic
|
||||
pass
|
||||
|
||||
@app.post("/users")
|
||||
async def create_user(
|
||||
user: UserCreate,
|
||||
background_tasks: BackgroundTasks
|
||||
):
|
||||
new_user = await create_user_in_db(user)
|
||||
background_tasks.add_task(send_email, user.email, "Welcome!")
|
||||
return new_user
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Use Pydantic models for request/response validation**
|
||||
2. **Organize routes with APIRouter**
|
||||
3. **Use dependency injection for services**
|
||||
4. **Return proper HTTP status codes**
|
||||
5. **Add OpenAPI descriptions and examples**
|
||||
|
||||
## Common Pitfalls
|
||||
|
||||
### Blocking I/O in Async
|
||||
|
||||
```python
|
||||
# ❌ BAD: Blocking call in async function
|
||||
@app.get("/users")
|
||||
async def list_users():
|
||||
users = blocking_db_call() # Blocks event loop
|
||||
return users
|
||||
|
||||
# ✅ GOOD: Use async libraries
|
||||
@app.get("/users")
|
||||
async def list_users(db: AsyncSession = Depends(get_db)):
|
||||
result = await db.execute(select(User))
|
||||
return result.scalars().all()
|
||||
```
|
||||
|
||||
### Missing Response Models
|
||||
|
||||
```python
|
||||
# ❌ BAD: No response model
|
||||
@app.get("/users/{user_id}")
|
||||
async def get_user(user_id: int):
|
||||
return await get_user_by_id(user_id)
|
||||
|
||||
# ✅ GOOD: Define response model
|
||||
@app.get("/users/{user_id}", response_model=UserResponse)
|
||||
async def get_user(user_id: int):
|
||||
return await get_user_by_id(user_id)
|
||||
```
|
||||
|
||||
### Not Using HTTPException
|
||||
|
||||
```python
|
||||
# ❌ BAD: Returning error dict
|
||||
@app.get("/users/{user_id}")
|
||||
async def get_user(user_id: int):
|
||||
user = await find_user(user_id)
|
||||
if not user:
|
||||
return {"error": "Not found"} # Returns 200!
|
||||
return user
|
||||
|
||||
# ✅ GOOD: Raise HTTPException
|
||||
@app.get("/users/{user_id}")
|
||||
async def get_user(user_id: int):
|
||||
user = await find_user(user_id)
|
||||
if not user:
|
||||
raise HTTPException(status_code=404, detail="User not found")
|
||||
return user
|
||||
```
|
||||
|
||||
## Advanced Patterns
|
||||
|
||||
### Middleware
|
||||
|
||||
```python
|
||||
from fastapi import Request
|
||||
import time
|
||||
|
||||
@app.middleware("http")
|
||||
async def add_process_time_header(request: Request, call_next):
|
||||
start_time = time.time()
|
||||
response = await call_next(request)
|
||||
process_time = time.time() - start_time
|
||||
response.headers["X-Process-Time"] = str(process_time)
|
||||
return response
|
||||
```
|
||||
|
||||
### Authentication
|
||||
|
||||
```python
|
||||
from fastapi import Security, HTTPException
|
||||
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
|
||||
|
||||
security = HTTPBearer()
|
||||
|
||||
async def get_current_user(
|
||||
credentials: HTTPAuthorizationCredentials = Security(security)
|
||||
) -> User:
|
||||
token = credentials.credentials
|
||||
user = await verify_token(token)
|
||||
if not user:
|
||||
raise HTTPException(status_code=401, detail="Invalid token")
|
||||
return user
|
||||
|
||||
@app.get("/profile")
|
||||
async def get_profile(current_user: User = Depends(get_current_user)):
|
||||
return current_user
|
||||
```
|
||||
|
||||
### CORS
|
||||
|
||||
```python
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
|
||||
app.add_middleware(
|
||||
CORSMiddleware,
|
||||
allow_origins=["https://example.com"],
|
||||
allow_credentials=True,
|
||||
allow_methods=["*"],
|
||||
allow_headers=["*"],
|
||||
)
|
||||
```
|
||||
|
||||
## Testing
|
||||
|
||||
```python
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
client = TestClient(app)
|
||||
|
||||
def test_create_user():
|
||||
response = client.post(
|
||||
"/users",
|
||||
json={"email": "test@example.com", "name": "Test"}
|
||||
)
|
||||
assert response.status_code == 201
|
||||
assert response.json()["email"] == "test@example.com"
|
||||
|
||||
def test_get_nonexistent_user():
|
||||
response = client.get("/users/999")
|
||||
assert response.status_code == 404
|
||||
```
|
||||
|
||||
## Related Skills
|
||||
|
||||
- [Python](/claudekit/skills/languages/python) - Python language
|
||||
- [Pydantic](/claudekit/skills/languages/python) - Data validation
|
||||
- [PostgreSQL](/claudekit/skills/databases/postgresql) - Database
|
||||
- [pytest](/claudekit/skills/testing/pytest) - Testing
|
||||
@@ -0,0 +1,404 @@
|
||||
---
|
||||
title: Next.js
|
||||
description: Next.js App Router, Server Components, and full-stack patterns
|
||||
---
|
||||
|
||||
The Next.js skill provides expertise in Next.js with App Router, Server Components, Server Actions, and full-stack development patterns.
|
||||
|
||||
## When Activated
|
||||
|
||||
- React applications with SSR/SSG
|
||||
- Full-stack applications
|
||||
- App Router patterns
|
||||
- Working in `app/` directory
|
||||
|
||||
## Core Patterns
|
||||
|
||||
### App Router Structure
|
||||
|
||||
```
|
||||
app/
|
||||
├── layout.tsx # Root layout
|
||||
├── page.tsx # Home page
|
||||
├── loading.tsx # Loading UI
|
||||
├── error.tsx # Error UI
|
||||
├── api/
|
||||
│ └── users/
|
||||
│ └── route.ts # API route
|
||||
└── users/
|
||||
├── page.tsx # Users page
|
||||
└── [id]/
|
||||
└── page.tsx # User detail
|
||||
```
|
||||
|
||||
### Server Components
|
||||
|
||||
```tsx
|
||||
// app/users/page.tsx - Server Component (default)
|
||||
async function UsersPage() {
|
||||
const users = await db.users.findMany();
|
||||
|
||||
return (
|
||||
<ul>
|
||||
{users.map(user => (
|
||||
<li key={user.id}>{user.name}</li>
|
||||
))}
|
||||
</ul>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### Client Components
|
||||
|
||||
```tsx
|
||||
'use client';
|
||||
|
||||
import { useState } from 'react';
|
||||
|
||||
export function Counter() {
|
||||
const [count, setCount] = useState(0);
|
||||
|
||||
return (
|
||||
<button onClick={() => setCount(c => c + 1)}>
|
||||
Count: {count}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### API Routes
|
||||
|
||||
```typescript
|
||||
// app/api/users/route.ts
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
|
||||
export async function GET() {
|
||||
const users = await db.users.findMany();
|
||||
return NextResponse.json(users);
|
||||
}
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
const data = await request.json();
|
||||
const user = await db.users.create({ data });
|
||||
return NextResponse.json(user, { status: 201 });
|
||||
}
|
||||
```
|
||||
|
||||
### Server Actions
|
||||
|
||||
```tsx
|
||||
// app/actions.ts
|
||||
'use server';
|
||||
|
||||
export async function createUser(formData: FormData) {
|
||||
const name = formData.get('name') as string;
|
||||
await db.users.create({ data: { name } });
|
||||
revalidatePath('/users');
|
||||
}
|
||||
|
||||
// app/users/new/page.tsx
|
||||
import { createUser } from '@/app/actions';
|
||||
|
||||
export default function NewUserPage() {
|
||||
return (
|
||||
<form action={createUser}>
|
||||
<input name="name" required />
|
||||
<button type="submit">Create</button>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### Dynamic Routes
|
||||
|
||||
```tsx
|
||||
// app/users/[id]/page.tsx
|
||||
interface PageProps {
|
||||
params: { id: string };
|
||||
}
|
||||
|
||||
export default async function UserPage({ params }: PageProps) {
|
||||
const user = await db.users.findUnique({
|
||||
where: { id: params.id }
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
notFound();
|
||||
}
|
||||
|
||||
return <div>{user.name}</div>;
|
||||
}
|
||||
```
|
||||
|
||||
### Layouts
|
||||
|
||||
```tsx
|
||||
// app/layout.tsx
|
||||
export default function RootLayout({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<body>
|
||||
<nav>{/* Navigation */}</nav>
|
||||
<main>{children}</main>
|
||||
<footer>{/* Footer */}</footer>
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Use Server Components by default**
|
||||
2. **Add 'use client' only when needed**
|
||||
3. **Colocate data fetching with components**
|
||||
4. **Use loading.tsx for suspense boundaries**
|
||||
5. **Implement proper error boundaries**
|
||||
|
||||
## Common Pitfalls
|
||||
|
||||
### Using Hooks in Server Components
|
||||
|
||||
```tsx
|
||||
// ❌ BAD: Hooks in Server Component
|
||||
export default async function Page() {
|
||||
const [state, setState] = useState(0); // Error!
|
||||
return <div>{state}</div>;
|
||||
}
|
||||
|
||||
// ✅ GOOD: Mark as Client Component
|
||||
'use client';
|
||||
|
||||
export default function Page() {
|
||||
const [state, setState] = useState(0);
|
||||
return <div>{state}</div>;
|
||||
}
|
||||
```
|
||||
|
||||
### Large Client Bundles
|
||||
|
||||
```tsx
|
||||
// ❌ BAD: Entire page as Client Component
|
||||
'use client';
|
||||
|
||||
export default function Page() {
|
||||
const [count, setCount] = useState(0);
|
||||
const data = await fetchData(); // Can't use await in Client Component
|
||||
|
||||
return (
|
||||
<div>
|
||||
<HeavyComponent />
|
||||
<button onClick={() => setCount(c => c + 1)}>{count}</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// ✅ GOOD: Extract interactive parts only
|
||||
export default async function Page() {
|
||||
const data = await fetchData(); // Server Component can await
|
||||
|
||||
return (
|
||||
<div>
|
||||
<HeavyComponent data={data} /> {/* Server Component */}
|
||||
<Counter /> {/* Small Client Component */}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Counter.tsx
|
||||
'use client';
|
||||
export function Counter() {
|
||||
const [count, setCount] = useState(0);
|
||||
return <button onClick={() => setCount(c => c + 1)}>{count}</button>;
|
||||
}
|
||||
```
|
||||
|
||||
### Missing Loading States
|
||||
|
||||
```tsx
|
||||
// ❌ BAD: No loading state
|
||||
export default async function Page() {
|
||||
const data = await slowFetch();
|
||||
return <div>{data}</div>;
|
||||
}
|
||||
|
||||
// ✅ GOOD: Add loading.tsx
|
||||
// app/page.tsx
|
||||
export default async function Page() {
|
||||
const data = await slowFetch();
|
||||
return <div>{data}</div>;
|
||||
}
|
||||
|
||||
// app/loading.tsx
|
||||
export default function Loading() {
|
||||
return <div>Loading...</div>;
|
||||
}
|
||||
```
|
||||
|
||||
### Not Revalidating After Mutations
|
||||
|
||||
```tsx
|
||||
// ❌ BAD: No revalidation
|
||||
'use server';
|
||||
export async function createUser(data: FormData) {
|
||||
await db.users.create({ data: getData(data) });
|
||||
// Page still shows old data
|
||||
}
|
||||
|
||||
// ✅ GOOD: Revalidate path
|
||||
'use server';
|
||||
export async function createUser(data: FormData) {
|
||||
await db.users.create({ data: getData(data) });
|
||||
revalidatePath('/users');
|
||||
redirect('/users');
|
||||
}
|
||||
```
|
||||
|
||||
## Data Fetching Patterns
|
||||
|
||||
### Parallel Data Fetching
|
||||
|
||||
```tsx
|
||||
export default async function Page() {
|
||||
// Fetch in parallel
|
||||
const [users, posts] = await Promise.all([
|
||||
fetchUsers(),
|
||||
fetchPosts(),
|
||||
]);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<UserList users={users} />
|
||||
<PostList posts={posts} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### Sequential Data Fetching
|
||||
|
||||
```tsx
|
||||
export default async function UserPage({ params }: { params: { id: string } }) {
|
||||
const user = await fetchUser(params.id);
|
||||
const posts = await fetchUserPosts(user.id); // Depends on user
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1>{user.name}</h1>
|
||||
<PostList posts={posts} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### Streaming with Suspense
|
||||
|
||||
```tsx
|
||||
import { Suspense } from 'react';
|
||||
|
||||
export default function Page() {
|
||||
return (
|
||||
<div>
|
||||
<h1>Dashboard</h1>
|
||||
<Suspense fallback={<Skeleton />}>
|
||||
<SlowComponent />
|
||||
</Suspense>
|
||||
<FastComponent />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
async function SlowComponent() {
|
||||
const data = await slowFetch();
|
||||
return <div>{data}</div>;
|
||||
}
|
||||
```
|
||||
|
||||
## Caching and Revalidation
|
||||
|
||||
### Time-Based Revalidation
|
||||
|
||||
```tsx
|
||||
// Revalidate every hour
|
||||
export const revalidate = 3600;
|
||||
|
||||
export default async function Page() {
|
||||
const data = await fetch('https://api.example.com/data', {
|
||||
next: { revalidate: 3600 }
|
||||
});
|
||||
return <div>{JSON.stringify(data)}</div>;
|
||||
}
|
||||
```
|
||||
|
||||
### On-Demand Revalidation
|
||||
|
||||
```tsx
|
||||
// app/actions.ts
|
||||
'use server';
|
||||
|
||||
import { revalidatePath, revalidateTag } from 'next/cache';
|
||||
|
||||
export async function createPost(data: FormData) {
|
||||
await db.posts.create({ data: getData(data) });
|
||||
revalidatePath('/posts');
|
||||
// or
|
||||
revalidateTag('posts');
|
||||
}
|
||||
```
|
||||
|
||||
## Metadata
|
||||
|
||||
```tsx
|
||||
import { Metadata } from 'next';
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: 'User Profile',
|
||||
description: 'View user profile information',
|
||||
};
|
||||
|
||||
export default function Page() {
|
||||
return <div>Profile</div>;
|
||||
}
|
||||
|
||||
// Dynamic metadata
|
||||
export async function generateMetadata({ params }: PageProps): Promise<Metadata> {
|
||||
const user = await fetchUser(params.id);
|
||||
|
||||
return {
|
||||
title: `${user.name}'s Profile`,
|
||||
description: `Profile page for ${user.name}`,
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
## Integration with Other Skills
|
||||
|
||||
### With React
|
||||
|
||||
See [React skill](/claudekit/skills/frameworks/react) for component patterns and hooks.
|
||||
|
||||
### With TypeScript
|
||||
|
||||
Full TypeScript integration:
|
||||
```tsx
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
|
||||
export async function GET(
|
||||
request: NextRequest,
|
||||
{ params }: { params: { id: string } }
|
||||
): Promise<NextResponse> {
|
||||
const user = await fetchUser(params.id);
|
||||
return NextResponse.json(user);
|
||||
}
|
||||
```
|
||||
|
||||
## Related Skills
|
||||
|
||||
- [React](/claudekit/skills/frameworks/react) - Component patterns
|
||||
- [TypeScript](/claudekit/skills/languages/typescript) - Type safety
|
||||
- [Tailwind CSS](/claudekit/skills/frontend/tailwind) - Styling
|
||||
- [PostgreSQL](/claudekit/skills/databases/postgresql) - Database
|
||||
@@ -0,0 +1,287 @@
|
||||
---
|
||||
title: React
|
||||
description: React component patterns, hooks, and state management
|
||||
---
|
||||
|
||||
The React skill provides expertise in React component patterns, hooks, context, and modern React best practices.
|
||||
|
||||
## When Activated
|
||||
|
||||
- Building React components
|
||||
- Using React hooks
|
||||
- Component state management
|
||||
- Working with `.jsx` or `.tsx` files containing React
|
||||
|
||||
## Core Patterns
|
||||
|
||||
### Functional Components
|
||||
|
||||
```tsx
|
||||
interface UserCardProps {
|
||||
user: User;
|
||||
onSelect?: (user: User) => void;
|
||||
}
|
||||
|
||||
export function UserCard({ user, onSelect }: UserCardProps) {
|
||||
return (
|
||||
<div className="card" onClick={() => onSelect?.(user)}>
|
||||
<h3>{user.name}</h3>
|
||||
<p>{user.email}</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### Hooks
|
||||
|
||||
```tsx
|
||||
// useState
|
||||
const [count, setCount] = useState(0);
|
||||
|
||||
// useEffect
|
||||
useEffect(() => {
|
||||
const subscription = subscribe();
|
||||
return () => subscription.unsubscribe();
|
||||
}, [dependency]);
|
||||
|
||||
// useMemo
|
||||
const expensive = useMemo(() => compute(data), [data]);
|
||||
|
||||
// useCallback
|
||||
const handleClick = useCallback(() => {
|
||||
doSomething(id);
|
||||
}, [id]);
|
||||
```
|
||||
|
||||
### Custom Hooks
|
||||
|
||||
```tsx
|
||||
function useUser(id: string) {
|
||||
const [user, setUser] = useState<User | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
setLoading(true);
|
||||
fetchUser(id)
|
||||
.then(setUser)
|
||||
.finally(() => setLoading(false));
|
||||
}, [id]);
|
||||
|
||||
return { user, loading };
|
||||
}
|
||||
|
||||
// Usage
|
||||
function UserProfile({ userId }: { userId: string }) {
|
||||
const { user, loading } = useUser(userId);
|
||||
|
||||
if (loading) return <div>Loading...</div>;
|
||||
if (!user) return <div>User not found</div>;
|
||||
|
||||
return <div>{user.name}</div>;
|
||||
}
|
||||
```
|
||||
|
||||
### Context Pattern
|
||||
|
||||
```tsx
|
||||
const UserContext = createContext<User | null>(null);
|
||||
|
||||
export function UserProvider({ children }: { children: ReactNode }) {
|
||||
const [user, setUser] = useState<User | null>(null);
|
||||
|
||||
return (
|
||||
<UserContext.Provider value={user}>
|
||||
{children}
|
||||
</UserContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
export function useUser() {
|
||||
const context = useContext(UserContext);
|
||||
if (!context) throw new Error('useUser must be within UserProvider');
|
||||
return context;
|
||||
}
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Keep components small and focused**
|
||||
2. **Use TypeScript for props**
|
||||
3. **Memoize expensive computations**
|
||||
4. **Clean up effects properly**
|
||||
5. **Lift state up when needed**
|
||||
|
||||
## Common Pitfalls
|
||||
|
||||
### Missing Dependencies in Hooks
|
||||
|
||||
```tsx
|
||||
// ❌ BAD: Missing dependency
|
||||
useEffect(() => {
|
||||
fetchData(userId);
|
||||
}, []); // userId not in dependencies
|
||||
|
||||
// ✅ GOOD: All dependencies included
|
||||
useEffect(() => {
|
||||
fetchData(userId);
|
||||
}, [userId]);
|
||||
```
|
||||
|
||||
### State Updates on Unmounted Components
|
||||
|
||||
```tsx
|
||||
// ❌ BAD: No cleanup
|
||||
useEffect(() => {
|
||||
fetchUser(id).then(setUser);
|
||||
}, [id]);
|
||||
|
||||
// ✅ GOOD: Cleanup function
|
||||
useEffect(() => {
|
||||
let cancelled = false;
|
||||
|
||||
fetchUser(id).then(user => {
|
||||
if (!cancelled) setUser(user);
|
||||
});
|
||||
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, [id]);
|
||||
```
|
||||
|
||||
### Prop Drilling
|
||||
|
||||
```tsx
|
||||
// ❌ BAD: Passing props through many levels
|
||||
<App>
|
||||
<Layout user={user}>
|
||||
<Header user={user}>
|
||||
<UserMenu user={user} />
|
||||
</Header>
|
||||
</Layout>
|
||||
</App>
|
||||
|
||||
// ✅ GOOD: Use context
|
||||
<UserProvider value={user}>
|
||||
<App>
|
||||
<Layout>
|
||||
<Header>
|
||||
<UserMenu /> {/* Gets user from context */}
|
||||
</Header>
|
||||
</Layout>
|
||||
</App>
|
||||
</UserProvider>
|
||||
```
|
||||
|
||||
### Not Memoizing Callbacks
|
||||
|
||||
```tsx
|
||||
// ❌ BAD: New function every render
|
||||
<Child onUpdate={(data) => handleUpdate(id, data)} />
|
||||
|
||||
// ✅ GOOD: Memoized callback
|
||||
const handleUpdate = useCallback((data) => {
|
||||
updateData(id, data);
|
||||
}, [id]);
|
||||
|
||||
<Child onUpdate={handleUpdate} />
|
||||
```
|
||||
|
||||
## Component Patterns
|
||||
|
||||
### Compound Components
|
||||
|
||||
```tsx
|
||||
function Tabs({ children }: { children: ReactNode }) {
|
||||
const [activeTab, setActiveTab] = useState(0);
|
||||
|
||||
return (
|
||||
<TabsContext.Provider value={{ activeTab, setActiveTab }}>
|
||||
{children}
|
||||
</TabsContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
Tabs.List = function TabsList({ children }: { children: ReactNode }) {
|
||||
return <div className="tabs-list">{children}</div>;
|
||||
};
|
||||
|
||||
Tabs.Tab = function Tab({ index, children }: { index: number; children: ReactNode }) {
|
||||
const { activeTab, setActiveTab } = useTabsContext();
|
||||
return (
|
||||
<button
|
||||
className={activeTab === index ? 'active' : ''}
|
||||
onClick={() => setActiveTab(index)}
|
||||
>
|
||||
{children}
|
||||
</button>
|
||||
);
|
||||
};
|
||||
|
||||
// Usage
|
||||
<Tabs>
|
||||
<Tabs.List>
|
||||
<Tabs.Tab index={0}>Tab 1</Tabs.Tab>
|
||||
<Tabs.Tab index={1}>Tab 2</Tabs.Tab>
|
||||
</Tabs.List>
|
||||
</Tabs>
|
||||
```
|
||||
|
||||
### Render Props
|
||||
|
||||
```tsx
|
||||
interface DataLoaderProps<T> {
|
||||
url: string;
|
||||
children: (data: T | null, loading: boolean) => ReactNode;
|
||||
}
|
||||
|
||||
function DataLoader<T>({ url, children }: DataLoaderProps<T>) {
|
||||
const [data, setData] = useState<T | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
fetch(url)
|
||||
.then(res => res.json())
|
||||
.then(setData)
|
||||
.finally(() => setLoading(false));
|
||||
}, [url]);
|
||||
|
||||
return <>{children(data, loading)}</>;
|
||||
}
|
||||
|
||||
// Usage
|
||||
<DataLoader<User> url="/api/user">
|
||||
{(user, loading) => (
|
||||
loading ? <Spinner /> : <UserProfile user={user} />
|
||||
)}
|
||||
</DataLoader>
|
||||
```
|
||||
|
||||
## Integration with Frameworks
|
||||
|
||||
### With Next.js
|
||||
|
||||
See [Next.js skill](/claudekit/skills/frameworks/nextjs) for server components and App Router patterns.
|
||||
|
||||
### With Testing
|
||||
|
||||
```tsx
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
|
||||
it('should increment count on click', async () => {
|
||||
render(<Counter />);
|
||||
|
||||
const button = screen.getByRole('button', { name: /increment/i });
|
||||
await userEvent.click(button);
|
||||
|
||||
expect(screen.getByText('Count: 1')).toBeInTheDocument();
|
||||
});
|
||||
```
|
||||
|
||||
## Related Skills
|
||||
|
||||
- [TypeScript](/claudekit/skills/languages/typescript) - Type safety
|
||||
- [Next.js](/claudekit/skills/frameworks/nextjs) - Full-stack React
|
||||
- [Tailwind CSS](/claudekit/skills/frontend/tailwind) - Styling
|
||||
- [vitest](/claudekit/skills/testing/vitest) - Testing
|
||||
Reference in New Issue
Block a user