## Skills Library - Browse and install 36+ community skills from Anthropic & Superpowers - Local skill templates (Rails, React, testing) - Create custom project-specific skills - Beautiful UI with search and filtering - /claude-os-skills command for CLI management ## Session Insights - Parse Claude Code native .jsonl session files - Extract patterns, decisions, blockers automatically - Track tool usage and file changes - Session summary generation for LLM processing ## Documentation - Updated README with v2.3 features - Updated API_REFERENCE with new endpoints - Added SKILLS_GUIDE.md and SESSIONS_GUIDE.md - Updated GitHub Pages (docs/index.html) - Reorganized docs (internal_docs for private) ## Bug Fixes - Fixed httpx client closure in community skill install - Fixed frontend request format for skill install 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
262 lines
6 KiB
Markdown
262 lines
6 KiB
Markdown
# React Patterns and Best Practices
|
|
|
|
## Purpose
|
|
|
|
Provides modern React patterns including:
|
|
- Component organization
|
|
- Hooks best practices
|
|
- State management patterns
|
|
- TypeScript integration
|
|
- Performance optimization
|
|
|
|
## Component Structure
|
|
|
|
```typescript
|
|
// src/components/UserProfile/UserProfile.tsx
|
|
import { FC, memo } from 'react';
|
|
import { useUserProfile } from './useUserProfile';
|
|
import { UserProfileProps } from './types';
|
|
import styles from './UserProfile.module.css';
|
|
|
|
export const UserProfile: FC<UserProfileProps> = memo(({ userId }) => {
|
|
const { user, isLoading, error } = useUserProfile(userId);
|
|
|
|
if (isLoading) return <LoadingSpinner />;
|
|
if (error) return <ErrorMessage error={error} />;
|
|
if (!user) return null;
|
|
|
|
return (
|
|
<div className={styles.container}>
|
|
<Avatar src={user.avatar} alt={user.name} />
|
|
<h2 className={styles.name}>{user.name}</h2>
|
|
<p className={styles.email}>{user.email}</p>
|
|
</div>
|
|
);
|
|
});
|
|
|
|
UserProfile.displayName = 'UserProfile';
|
|
```
|
|
|
|
## Custom Hooks
|
|
|
|
```typescript
|
|
// src/hooks/useAsync.ts
|
|
import { useState, useCallback } from 'react';
|
|
|
|
interface AsyncState<T> {
|
|
data: T | null;
|
|
isLoading: boolean;
|
|
error: Error | null;
|
|
}
|
|
|
|
export function useAsync<T>() {
|
|
const [state, setState] = useState<AsyncState<T>>({
|
|
data: null,
|
|
isLoading: false,
|
|
error: null,
|
|
});
|
|
|
|
const execute = useCallback(async (promise: Promise<T>) => {
|
|
setState({ data: null, isLoading: true, error: null });
|
|
try {
|
|
const data = await promise;
|
|
setState({ data, isLoading: false, error: null });
|
|
return data;
|
|
} catch (error) {
|
|
setState({ data: null, isLoading: false, error: error as Error });
|
|
throw error;
|
|
}
|
|
}, []);
|
|
|
|
return { ...state, execute };
|
|
}
|
|
```
|
|
|
|
## API Hook Pattern
|
|
|
|
```typescript
|
|
// src/hooks/useApi.ts
|
|
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
|
import { api } from '../api';
|
|
|
|
export function useUsers() {
|
|
return useQuery({
|
|
queryKey: ['users'],
|
|
queryFn: api.getUsers,
|
|
staleTime: 5 * 60 * 1000, // 5 minutes
|
|
});
|
|
}
|
|
|
|
export function useCreateUser() {
|
|
const queryClient = useQueryClient();
|
|
|
|
return useMutation({
|
|
mutationFn: api.createUser,
|
|
onSuccess: () => {
|
|
queryClient.invalidateQueries({ queryKey: ['users'] });
|
|
},
|
|
});
|
|
}
|
|
```
|
|
|
|
## Context Pattern
|
|
|
|
```typescript
|
|
// src/context/AuthContext.tsx
|
|
import { createContext, useContext, useState, ReactNode } from 'react';
|
|
|
|
interface AuthContextType {
|
|
user: User | null;
|
|
login: (credentials: Credentials) => Promise<void>;
|
|
logout: () => void;
|
|
isAuthenticated: boolean;
|
|
}
|
|
|
|
const AuthContext = createContext<AuthContextType | undefined>(undefined);
|
|
|
|
export function AuthProvider({ children }: { children: ReactNode }) {
|
|
const [user, setUser] = useState<User | null>(null);
|
|
|
|
const login = async (credentials: Credentials) => {
|
|
const user = await authApi.login(credentials);
|
|
setUser(user);
|
|
};
|
|
|
|
const logout = () => {
|
|
setUser(null);
|
|
authApi.logout();
|
|
};
|
|
|
|
return (
|
|
<AuthContext.Provider value={{
|
|
user,
|
|
login,
|
|
logout,
|
|
isAuthenticated: !!user
|
|
}}>
|
|
{children}
|
|
</AuthContext.Provider>
|
|
);
|
|
}
|
|
|
|
export function useAuth() {
|
|
const context = useContext(AuthContext);
|
|
if (!context) {
|
|
throw new Error('useAuth must be used within AuthProvider');
|
|
}
|
|
return context;
|
|
}
|
|
```
|
|
|
|
## Form Pattern (React Hook Form)
|
|
|
|
```typescript
|
|
// src/components/UserForm/UserForm.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(2, 'Name must be at least 2 characters'),
|
|
email: z.string().email('Invalid email address'),
|
|
});
|
|
|
|
type FormData = z.infer<typeof schema>;
|
|
|
|
export function UserForm({ onSubmit }: { onSubmit: (data: FormData) => void }) {
|
|
const {
|
|
register,
|
|
handleSubmit,
|
|
formState: { errors, isSubmitting },
|
|
} = useForm<FormData>({
|
|
resolver: zodResolver(schema),
|
|
});
|
|
|
|
return (
|
|
<form onSubmit={handleSubmit(onSubmit)}>
|
|
<input {...register('name')} placeholder="Name" />
|
|
{errors.name && <span>{errors.name.message}</span>}
|
|
|
|
<input {...register('email')} placeholder="Email" />
|
|
{errors.email && <span>{errors.email.message}</span>}
|
|
|
|
<button type="submit" disabled={isSubmitting}>
|
|
{isSubmitting ? 'Saving...' : 'Save'}
|
|
</button>
|
|
</form>
|
|
);
|
|
}
|
|
```
|
|
|
|
## Error Boundary
|
|
|
|
```typescript
|
|
// src/components/ErrorBoundary.tsx
|
|
import { Component, ErrorInfo, ReactNode } from 'react';
|
|
|
|
interface Props {
|
|
children: ReactNode;
|
|
fallback?: ReactNode;
|
|
}
|
|
|
|
interface State {
|
|
hasError: boolean;
|
|
error?: Error;
|
|
}
|
|
|
|
export class ErrorBoundary extends Component<Props, State> {
|
|
state: State = { hasError: false };
|
|
|
|
static getDerivedStateFromError(error: Error): State {
|
|
return { hasError: true, error };
|
|
}
|
|
|
|
componentDidCatch(error: Error, errorInfo: ErrorInfo) {
|
|
console.error('Error caught by boundary:', error, errorInfo);
|
|
}
|
|
|
|
render() {
|
|
if (this.state.hasError) {
|
|
return this.props.fallback || <div>Something went wrong</div>;
|
|
}
|
|
return this.props.children;
|
|
}
|
|
}
|
|
```
|
|
|
|
## Performance Patterns
|
|
|
|
```typescript
|
|
// Memoization
|
|
const ExpensiveComponent = memo(({ data }) => {
|
|
// Only re-renders when data changes
|
|
return <div>{/* ... */}</div>;
|
|
});
|
|
|
|
// useMemo for expensive calculations
|
|
const sortedItems = useMemo(() => {
|
|
return items.sort((a, b) => a.name.localeCompare(b.name));
|
|
}, [items]);
|
|
|
|
// useCallback for stable function references
|
|
const handleClick = useCallback((id: string) => {
|
|
setSelected(id);
|
|
}, []);
|
|
|
|
// Lazy loading
|
|
const HeavyComponent = lazy(() => import('./HeavyComponent'));
|
|
|
|
<Suspense fallback={<Loading />}>
|
|
<HeavyComponent />
|
|
</Suspense>
|
|
```
|
|
|
|
## Key Principles
|
|
|
|
1. **Composition over inheritance** - Compose components
|
|
2. **Single responsibility** - One component, one job
|
|
3. **Lift state up** - Share state at common ancestor
|
|
4. **Custom hooks** - Extract reusable logic
|
|
5. **TypeScript first** - Type everything
|
|
6. **Memoize wisely** - Don't over-optimize
|
|
7. **Error boundaries** - Graceful error handling
|