Files
APAW/.kilo/skills/react-patterns/SKILL.md
¨NW¨ 7445e66676 feat: add Next.js, Vue/Nuxt, React, Python (Django/FastAPI) skills and agents
- python-developer agent: Django/FastAPI backend specialist
- nextjs-patterns skill: App Router, Server Components, Server Actions, Auth.js
- vue-nuxt-patterns skill: Composition API, Pinia, Nitro server, SSR
- react-patterns skill: hooks, Context, TanStack Query, React Hook Form
- python-django-patterns skill: DRF, services, repositories
- python-fastapi-patterns skill: async, Pydantic, SQLAlchemy, dependencies
- /nextjs pipeline command for full-stack Next.js apps
- /vue pipeline command for full-stack Vue/Nuxt apps
- Updated frontend-developer with framework-specific skills
- Updated orchestrator, capability-index for Python + frontend routing
- Updated README, STRUCTURE, EVOLUTION_LOG with all new stacks

Total agents: 30. Stacks: PHP, Next.js, Vue/Nuxt, React, Python, Go, Flutter, Node.js
2026-04-19 10:04:51 +01:00

8.3 KiB

name, description
name description
react-patterns React 18+ patterns — Hooks, composition, context, suspense, concurrent features, component architecture

React Patterns

Project Structure

src/
├── app/ or pages/              # Next.js or Vite pages
├── components/
│   ├── ui/                     # Base UI primitives
│   │   ├── Button.tsx
│   │   ├── Input.tsx
│   │   ├── Modal.tsx
│   │   └── Card.tsx
│   ├── forms/                  # Form components
│   │   ├── LoginForm.tsx
│   │   └── ProductForm.tsx
│   └── features/              # Feature components
│       ├── product/
│       ├── cart/
│       └── auth/
├── hooks/                      # Custom hooks
│   ├── useAuth.ts
│   ├── useCart.ts
│   ├── useDebounce.ts
│   └── useApi.ts
├── context/                    # React Context providers
│   ├── AuthContext.tsx
│   └── CartContext.tsx
├── services/                   # API services
│   ├── api.ts                  # Axios/fetch instance
│   ├── authService.ts
│   └── productService.ts
├── types/                      # TypeScript types
│   └── index.ts
├── utils/                      # Utilities
│   ├── format.ts
│   └── validation.ts
└── lib/                        # Third-party config
    ├── queryClient.ts          # TanStack Query
    └── supabase.ts

Component Pattern

// components/product/ProductCard.tsx
import { useState } from 'react';
import { useCart } from '@/hooks/useCart';

interface ProductCardProps {
  product: Product;
  showAddToCart?: boolean;
  onAddToCart?: (productId: string) => void;
}

export function ProductCard({ product, showAddToCart = true, onAddToCart }: ProductCardProps) {
  const [isAdding, setIsAdding] = useState(false);
  const { addItem } = useCart();

  const handleAdd = async () => {
    setIsAdding(true);
    await addItem(product.id, 1);
    onAddToCart?.(product.id);
    setIsAdding(false);
  };

  const formattedPrice = new Intl.NumberFormat('en-US', {
    style: 'currency', currency: 'USD',
  }).format(product.price);

  return (
    <div className="border rounded-lg p-4 hover:shadow-lg transition-shadow">
      <a href={`/products/${product.id}`}>
        <img src={product.image} alt={product.name} className="w-full h-48 object-cover rounded" />
        <h3 className="mt-2 font-semibold">{product.name}</h3>
        <p className="text-gray-600">{formattedPrice}</p>
      </a>

      {showAddToCart && (
        <button
          onClick={handleAdd}
          disabled={isAdding}
          className="mt-2 w-full bg-blue-600 text-white py-2 rounded hover:bg-blue-700 disabled:opacity-50"
        >
          {isAdding ? 'Adding...' : 'Add to Cart'}
        </button>
      )}
    </div>
  );
}

Custom Hook Pattern

// hooks/useCart.ts
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { cartService } from '@/services/cartService';

export function useCart() {
  const queryClient = useQueryClient();

  const { data: cart } = useQuery({
    queryKey: ['cart'],
    queryFn: cartService.get,
  });

  const addItemMutation = useMutation({
    mutationFn: cartService.addItem,
    onSuccess: () => queryClient.invalidateQueries({ queryKey: ['cart'] }),
  });

  const removeItemMutation = useMutation({
    mutationFn: cartService.removeItem,
    onSuccess: () => queryClient.invalidateQueries({ queryKey: ['cart'] }),
  });

  const total = cart?.items?.reduce((sum, item) => sum + item.price * item.quantity, 0) ?? 0;
  const count = cart?.items?.reduce((sum, item) => sum + item.quantity, 0) ?? 0;

  return {
    cart,
    total,
    count,
    addItem: addItemMutation.mutateAsync,
    removeItem: removeItemMutation.mutateAsync,
    isAdding: addItemMutation.isPending,
  };
}

Context Pattern

// context/AuthContext.tsx
import { createContext, useContext, useState, useCallback, type ReactNode } from 'react';

interface AuthContextType {
  user: User | null;
  isAuthenticated: boolean;
  login: (email: string, password: string) => Promise<void>;
  logout: () => void;
}

const AuthContext = createContext<AuthContextType | null>(null);

export function AuthProvider({ children }: { children: ReactNode }) {
  const [user, setUser] = useState<User | null>(null);

  const login = useCallback(async (email: string, password: string) => {
    const response = await authService.login({ email, password });
    setUser(response.user);
    localStorage.setItem('token', response.token);
  }, []);

  const logout = useCallback(() => {
    setUser(null);
    localStorage.removeItem('token');
  }, []);

  return (
    <AuthContext.Provider value={{ user, isAuthenticated: !!user, login, logout }}>
      {children}
    </AuthContext.Provider>
  );
}

export function useAuth() {
  const context = useContext(AuthContext);
  if (!context) throw new Error('useAuth must be used within AuthProvider');
  return context;
}

API Service Pattern

// services/api.ts
import axios from 'axios';

export const api = axios.create({
  baseURL: import.meta.env.VITE_API_URL || '/api',
  headers: { 'Content-Type': 'application/json' },
});

api.interceptors.request.use((config) => {
  const token = localStorage.getItem('token');
  if (token) config.headers.Authorization = `Bearer ${token}`;
  return config;
});

api.interceptors.response.use(
  (response) => response,
  (error) => {
    if (error.response?.status === 401) {
      localStorage.removeItem('token');
      window.location.href = '/login';
    }
    return Promise.reject(error);
  }
);

// services/productService.ts
import { api } from './api';

export const productService = {
  list: (params?: object) => api.get('/products', { params }).then((r) => r.data),
  get: (id: string) => api.get(`/products/${id}`).then((r) => r.data),
  create: (data: CreateProductDTO) => api.post('/products', data).then((r) => r.data),
  update: (id: string, data: UpdateProductDTO) => api.put(`/products/${id}`, data).then((r) => r.data),
  delete: (id: string) => api.delete(`/products/${id}`).then((r) => r.data),
};

Form Pattern (React Hook Form + Zod)

// components/forms/ProductForm.tsx
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';

const schema = z.object({
  name: z.string().min(1, 'Name is required').max(255),
  price: z.number().positive('Price must be positive'),
  categoryId: z.string().min(1, 'Category is required'),
  description: z.string().optional(),
});

type ProductFormData = z.infer<typeof schema>;

export function ProductForm({ onSubmit }: { onSubmit: (data: ProductFormData) => Promise<void> }) {
  const { register, handleSubmit, formState: { errors, isSubmitting } } = useForm<ProductFormData>({
    resolver: zodResolver(schema),
  });

  return (
    <form onSubmit={handleSubmit(onSubmit)} className="space-y-4">
      <div>
        <label>Name</label>
        <input {...register('name')} className="border rounded px-3 py-2 w-full" />
        {errors.name && <p className="text-red-500 text-sm">{errors.name.message}</p>}
      </div>

      <div>
        <label>Price</label>
        <input type="number" step="0.01" {...register('price', { valueAsNumber: true })} className="border rounded px-3 py-2 w-full" />
        {errors.price && <p className="text-red-500 text-sm">{errors.price.message}</p>}
      </div>

      <button type="submit" disabled={isSubmitting} className="bg-blue-600 text-white px-4 py-2 rounded">
        {isSubmitting ? 'Saving...' : 'Save Product'}
      </button>
    </form>
  );
}

Checklist

  • Functional components with hooks (no class components)
  • TypeScript with strict mode
  • interface for props, type for unions
  • Custom hooks for reusable logic (data fetching, auth, forms)
  • React Hook Form + Zod for form validation
  • TanStack Query for server state (not useState for API data)
  • Context only for global state (auth, theme)
  • React.memo for expensive renders only
  • Error boundaries for crash recovery
  • Suspense for loading states
  • useCallback/useMemo only when needed (not by default)
  • Clean up effects (return cleanup function)